Skip to content

Commit 2cbffbd

Browse files
author
Artur Shiriev
committed
update docs
1 parent c131e7f commit 2cbffbd

File tree

3 files changed

+210
-2
lines changed

3 files changed

+210
-2
lines changed

AGENTS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ The library is designed to be used as a dependency in other projects. To use it:
8383
router = Router()
8484

8585
@router.handler(schema=MySchema)
86-
async def my_timer_handler(data: MySchema):
86+
async def my_timer_handler(data: MySchema, context: dict):
8787
# Handle timer event
8888
pass
8989
```
@@ -157,7 +157,7 @@ Handlers are registered using decorators:
157157

158158
```python
159159
@router.handler(name="custom_name", schema=MySchema)
160-
async def my_handler(data: MySchema):
160+
async def my_handler(data: MySchema, context: dict):
161161
# Process timer event
162162
pass
163163
```

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2024 modern-python
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
# Redis Timers
2+
3+
[![MyPy Strict](https://img.shields.io/badge/mypy-strict-blue)](https://mypy.readthedocs.io/en/stable/getting_started.html#strict-mode-and-configuration)
4+
[![Supported versions](https://img.shields.io/pypi/pyversions/redis-timers.svg)](https://pypi.python.org/pypi/redis-timers)
5+
[![downloads](https://img.shields.io/pypi/dm/redis-timers.svg)](https://pypistats.org/packages/redis-timers)
6+
[![GitHub stars](https://img.shields.io/github/stars/modern-python/redis-timers)](https://github.com/modern-python/redis-timers/stargazers)
7+
8+
Redis Timers is a Python library that provides a robust framework for managing timed events using Redis as the backend.
9+
10+
It allows you to schedule timers that trigger handlers at specific times, with payloads automatically validated using Pydantic schemas.
11+
12+
## Features
13+
14+
- Schedule timers with custom payloads
15+
- Automatic payload validation using Pydantic
16+
- Distributed lock mechanism to prevent duplicate processing
17+
- Topic-based routing for timer handlers
18+
- Automatic cleanup of processed timers
19+
- Built with asyncio for high performance
20+
21+
## 📦 [PyPi](https://pypi.org/project/redis-timers)
22+
23+
## 📝 [License](LICENSE)
24+
25+
26+
## Installation
27+
28+
```bash
29+
pip install redis-timers
30+
```
31+
32+
## Quick Start
33+
34+
```python
35+
import asyncio
36+
import datetime
37+
from redis import asyncio as aioredis
38+
from pydantic import BaseModel
39+
40+
from redis_timers import Timers, Router
41+
42+
# Define your payload schema
43+
class MyPayload(BaseModel):
44+
message: str
45+
count: int
46+
47+
# Create a router and register handlers
48+
router = Router()
49+
50+
@router.handler(topic="my_topic", schema=MyPayload)
51+
async def my_timer_handler(data: MyPayload, context: dict):
52+
print(f"Timer triggered: {data.message} (count: {data.count})")
53+
54+
# Initialize Redis client
55+
redis_client = aioredis.Redis.from_url("redis://localhost:6379", decode_responses=True)
56+
57+
# Initialize timers
58+
timers = Timers(redis_client=redis_client, context={"app_name": "my_app"})
59+
timers.include_router(router)
60+
61+
# Schedule a timer
62+
async def schedule_timer():
63+
payload = MyPayload(message="Hello, World!", count=42)
64+
await timers.set_timer(
65+
topic="my_topic",
66+
timer_id="unique_timer_id",
67+
payload=payload,
68+
activation_period=datetime.timedelta(minutes=5)
69+
)
70+
71+
# Run the timer processing loop
72+
async def main():
73+
# Schedule a timer
74+
await schedule_timer()
75+
76+
# Process timers continuously
77+
await timers.run_forever()
78+
79+
if __name__ == "__main__":
80+
asyncio.run(main())
81+
```
82+
83+
## Core Concepts
84+
85+
### Timers
86+
87+
The `Timers` class is the main interface for managing timed events. It connects to Redis and handles the scheduling, execution, and cleanup of timers.
88+
89+
### Router
90+
91+
The `Router` class is used to register timer handlers. Handlers are functions that get executed when a timer expires.
92+
93+
### Handlers
94+
95+
Handlers are async functions decorated with `@router.handler()` that process timer events. Each handler is associated with a topic and a Pydantic schema for payload validation.
96+
97+
### Payloads
98+
99+
Payloads are Pydantic models that contain the data associated with a timer. They are automatically validated when timers are processed.
100+
101+
## API Reference
102+
103+
### Timers
104+
105+
#### `Timers(redis_client, context={})`
106+
107+
Initialize a Timers instance.
108+
109+
- `redis_client`: An async Redis client instance
110+
- `context`: A dictionary of context data passed to handlers
111+
112+
#### `timers.include_router(router)`
113+
114+
Include a router with its handlers.
115+
116+
#### `timers.include_routers(*routers)`
117+
118+
Include multiple routers.
119+
120+
#### `timers.set_timer(topic, timer_id, payload, activation_period)`
121+
122+
Schedule a timer.
123+
124+
- `topic`: The topic associated with the timer handler
125+
- `timer_id`: A unique identifier for the timer
126+
- `payload`: A Pydantic model instance containing the timer data
127+
- `activation_period`: A timedelta specifying when the timer should trigger
128+
129+
#### `timers.remove_timer(topic, timer_id)`
130+
131+
Remove a scheduled timer.
132+
133+
#### `timers.handle_ready_timers()`
134+
135+
Process all timers that are ready to be triggered.
136+
137+
#### `timers.run_forever()`
138+
139+
Continuously process timers in a loop.
140+
141+
### Router
142+
143+
#### `Router()`
144+
145+
Create a new router instance.
146+
147+
#### `router.handler(topic, schema)`
148+
149+
Decorator for registering timer handlers.
150+
151+
- `topic`: The topic to associate with this handler
152+
- `schema`: The Pydantic model class for payload validation
153+
154+
## Configuration
155+
156+
Redis Timers can be configured using environment variables:
157+
158+
- `TIMERS_TIMELINE_KEY`: Redis key for the sorted set storing timer timestamps (default: "timers_timeline")
159+
- `TIMERS_PAYLOADS_KEY`: Redis key for the hash storing timer payloads (default: "timers_payloads")
160+
- `TIMERS_HANDLING_SLEEP`: Base sleep time between timer processing cycles (default: 0.05 seconds)
161+
- `TIMERS_HANDLING_JITTER_MIN_VALUE`: Minimum jitter multiplier (default: 0.5)
162+
- `TIMERS_HANDLING_JITTER_MAX_VALUE`: Maximum jitter multiplier (default: 2.0)
163+
- `TIMERS_SEPARATOR`: Separator used between topic and timer ID (default: "--")
164+
165+
## How It Works
166+
167+
1. **Timer Scheduling**: When you call `set_timer()`, Redis Timers stores:
168+
- The timer's activation timestamp in a Redis sorted set (`timers_timeline`)
169+
- The timer's payload as JSON in a Redis hash (`timers_payloads`)
170+
- The timer key is constructed as `{topic}{separator}{timer_id}`
171+
172+
2. **Timer Processing**: The `run_forever()` method continuously:
173+
- Checks for timers that should be triggered (current timestamp >= activation timestamp)
174+
- Acquires a distributed lock to prevent duplicate processing
175+
- Validates the payload using the registered Pydantic schema
176+
- Calls the appropriate handler function
177+
- Removes the processed timer from Redis
178+
179+
3. **Distributed Locking**: Redis Timers uses Redis locks to ensure that:
180+
- The same timer isn't processed multiple times in distributed environments
181+
- Concurrent modifications to the same timer are prevented
182+
183+
## Requirements
184+
185+
- Python 3.13+
186+
- Redis server
187+
- Dependencies: `pydantic`, `redis`

0 commit comments

Comments
 (0)