rrq 0.7.1__tar.gz → 0.8.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. rrq-0.8.1/AGENTS.md +32 -0
  2. rrq-0.8.1/CLAUDE.md +32 -0
  3. {rrq-0.7.1 → rrq-0.8.1}/PKG-INFO +49 -5
  4. {rrq-0.7.1 → rrq-0.8.1}/README.md +46 -4
  5. {rrq-0.7.1 → rrq-0.8.1}/example/rrq_example.py +10 -15
  6. {rrq-0.7.1 → rrq-0.8.1}/pyproject.toml +8 -8
  7. {rrq-0.7.1 → rrq-0.8.1}/rrq/cli.py +5 -3
  8. {rrq-0.7.1 → rrq-0.8.1}/rrq/cli_commands/base.py +4 -1
  9. {rrq-0.7.1 → rrq-0.8.1}/rrq/cli_commands/commands/debug.py +2 -2
  10. {rrq-0.7.1 → rrq-0.8.1}/rrq/cli_commands/commands/monitor.py +92 -60
  11. {rrq-0.7.1 → rrq-0.8.1}/rrq/cli_commands/commands/queues.py +2 -2
  12. {rrq-0.7.1 → rrq-0.8.1}/rrq/cli_commands/utils.py +5 -4
  13. rrq-0.8.1/rrq/client.py +196 -0
  14. rrq-0.8.1/rrq/exporters/__init__.py +1 -0
  15. rrq-0.8.1/rrq/exporters/prometheus.py +90 -0
  16. rrq-0.8.1/rrq/exporters/statsd.py +60 -0
  17. {rrq-0.7.1 → rrq-0.8.1}/rrq/hooks.py +80 -47
  18. rrq-0.8.1/rrq/integrations/__init__.py +1 -0
  19. rrq-0.8.1/rrq/integrations/ddtrace.py +456 -0
  20. rrq-0.8.1/rrq/integrations/logfire.py +23 -0
  21. rrq-0.8.1/rrq/integrations/otel.py +325 -0
  22. {rrq-0.7.1 → rrq-0.8.1}/rrq/job.py +6 -0
  23. {rrq-0.7.1 → rrq-0.8.1}/rrq/settings.py +2 -2
  24. {rrq-0.7.1 → rrq-0.8.1}/rrq/store.py +49 -6
  25. rrq-0.8.1/rrq/telemetry.py +129 -0
  26. {rrq-0.7.1 → rrq-0.8.1}/rrq/worker.py +259 -94
  27. {rrq-0.7.1 → rrq-0.8.1}/tests/cli_commands/conftest.py +5 -8
  28. {rrq-0.7.1 → rrq-0.8.1}/tests/cli_commands/test_debug_commands.py +7 -4
  29. {rrq-0.7.1 → rrq-0.8.1}/tests/cli_commands/test_dlq_commands.py +1 -0
  30. {rrq-0.7.1 → rrq-0.8.1}/tests/cli_commands/test_integration.py +5 -5
  31. {rrq-0.7.1 → rrq-0.8.1}/tests/cli_commands/test_job_commands.py +1 -0
  32. {rrq-0.7.1 → rrq-0.8.1}/tests/cli_commands/test_monitor_commands.py +18 -10
  33. {rrq-0.7.1 → rrq-0.8.1}/tests/cli_commands/test_queue_commands.py +1 -0
  34. {rrq-0.7.1 → rrq-0.8.1}/tests/cli_commands/test_queue_dlq_integration.py +1 -0
  35. {rrq-0.7.1 → rrq-0.8.1}/tests/test_cli.py +6 -1
  36. {rrq-0.7.1 → rrq-0.8.1}/tests/test_client.py +55 -14
  37. {rrq-0.7.1 → rrq-0.8.1}/tests/test_cron.py +6 -2
  38. {rrq-0.7.1 → rrq-0.8.1}/tests/test_registry.py +3 -1
  39. {rrq-0.7.1 → rrq-0.8.1}/tests/test_store.py +87 -2
  40. {rrq-0.7.1 → rrq-0.8.1}/tests/test_worker.py +56 -13
  41. rrq-0.8.1/uv.lock +740 -0
  42. rrq-0.7.1/CLAUDE.md +0 -115
  43. rrq-0.7.1/rrq/client.py +0 -186
  44. rrq-0.7.1/uv.lock +0 -724
  45. {rrq-0.7.1 → rrq-0.8.1}/.coverage +0 -0
  46. {rrq-0.7.1 → rrq-0.8.1}/.github/workflows/ci.yml +0 -0
  47. {rrq-0.7.1 → rrq-0.8.1}/.gitignore +0 -0
  48. {rrq-0.7.1 → rrq-0.8.1}/LICENSE +0 -0
  49. {rrq-0.7.1 → rrq-0.8.1}/MANIFEST.in +0 -0
  50. {rrq-0.7.1 → rrq-0.8.1}/docs/CLI_REFERENCE.md +0 -0
  51. {rrq-0.7.1 → rrq-0.8.1}/example/example_rrq_settings.py +0 -0
  52. {rrq-0.7.1 → rrq-0.8.1}/rrq/__init__.py +0 -0
  53. {rrq-0.7.1 → rrq-0.8.1}/rrq/cli_commands/__init__.py +0 -0
  54. {rrq-0.7.1 → rrq-0.8.1}/rrq/cli_commands/commands/__init__.py +0 -0
  55. {rrq-0.7.1 → rrq-0.8.1}/rrq/cli_commands/commands/dlq.py +0 -0
  56. {rrq-0.7.1 → rrq-0.8.1}/rrq/cli_commands/commands/jobs.py +0 -0
  57. {rrq-0.7.1 → rrq-0.8.1}/rrq/constants.py +0 -0
  58. {rrq-0.7.1 → rrq-0.8.1}/rrq/cron.py +0 -0
  59. {rrq-0.7.1 → rrq-0.8.1}/rrq/exc.py +0 -0
  60. {rrq-0.7.1 → rrq-0.8.1}/rrq/registry.py +0 -0
  61. {rrq-0.7.1 → rrq-0.8.1}/tests/CLAUDE.md +0 -0
  62. {rrq-0.7.1 → rrq-0.8.1}/tests/__init__.py +0 -0
  63. {rrq-0.7.1 → rrq-0.8.1}/tests/cli_commands/__init__.py +0 -0
  64. {rrq-0.7.1 → rrq-0.8.1}/tests/cli_commands/test_monitor_dlq_integration.py +0 -0
rrq-0.8.1/AGENTS.md ADDED
@@ -0,0 +1,32 @@
1
+ # RRQ
2
+
3
+ Redis-based async job queue library for Python.
4
+
5
+ See @tests/CLAUDE.md for testing guidelines.
6
+
7
+ ## Commands
8
+ ```bash
9
+ uv run pytest # Run tests
10
+ uv run pytest --maxfail=1 # Debug failing tests
11
+ uv run ruff format && uv run ruff check --fix # Format and lint (run before commits)
12
+ uv run ty check # Type check (must pass before commits)
13
+ uv add <package> # Add dependency
14
+ ```
15
+
16
+ ## Code Style
17
+ - Python 3.11+, double quotes, 88 char lines
18
+ - Type hints on all functions, Pydantic V2 for validation
19
+ - `snake_case` functions, `PascalCase` classes
20
+ - Import order: stdlib → third-party → local
21
+ - Early returns, `match/case` for complex conditionals
22
+ - No blocking I/O in async contexts
23
+
24
+ ## Code References
25
+ Use VS Code clickable format: `rrq/queue.py:45` or `rrq/worker.py:120-135`
26
+
27
+ ## Rules
28
+ - Never commit broken tests
29
+ - Use `uv` for all Python operations
30
+ - Follow existing patterns in codebase
31
+ - No sensitive data in logs
32
+ - Ask before large cross-domain changes
rrq-0.8.1/CLAUDE.md ADDED
@@ -0,0 +1,32 @@
1
+ # RRQ
2
+
3
+ Redis-based async job queue library for Python.
4
+
5
+ See @tests/CLAUDE.md for testing guidelines.
6
+
7
+ ## Commands
8
+ ```bash
9
+ uv run pytest # Run tests
10
+ uv run pytest --maxfail=1 # Debug failing tests
11
+ uv run ruff format && uv run ruff check --fix # Format and lint (run before commits)
12
+ uv run ty check # Type check (must pass before commits)
13
+ uv add <package> # Add dependency
14
+ ```
15
+
16
+ ## Code Style
17
+ - Python 3.11+, double quotes, 88 char lines
18
+ - Type hints on all functions, Pydantic V2 for validation
19
+ - `snake_case` functions, `PascalCase` classes
20
+ - Import order: stdlib → third-party → local
21
+ - Early returns, `match/case` for complex conditionals
22
+ - No blocking I/O in async contexts
23
+
24
+ ## Code References
25
+ Use VS Code clickable format: `rrq/queue.py:45` or `rrq/worker.py:120-135`
26
+
27
+ ## Rules
28
+ - Never commit broken tests
29
+ - Use `uv` for all Python operations
30
+ - Follow existing patterns in codebase
31
+ - No sensitive data in logs
32
+ - Ask before large cross-domain changes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rrq
3
- Version: 0.7.1
3
+ Version: 0.8.1
4
4
  Summary: RRQ is a Python library for creating reliable job queues using Redis and asyncio
5
5
  Project-URL: Homepage, https://github.com/getresq/rrq
6
6
  Project-URL: Bug Tracker, https://github.com/getresq/rrq/issues
@@ -26,23 +26,30 @@ Provides-Extra: dev
26
26
  Requires-Dist: pytest-asyncio>=1.0.0; extra == 'dev'
27
27
  Requires-Dist: pytest-cov>=6.0.0; extra == 'dev'
28
28
  Requires-Dist: pytest>=8.3.5; extra == 'dev'
29
+ Requires-Dist: ruff==0.14.9; extra == 'dev'
30
+ Requires-Dist: ty==0.0.1-alpha.26; extra == 'dev'
29
31
  Description-Content-Type: text/markdown
30
32
 
31
33
  # RRQ: Reliable Redis Queue
32
34
 
33
35
  RRQ is a Python library for creating reliable job queues using Redis and `asyncio`, inspired by [ARQ (Async Redis Queue)](https://github.com/samuelcolvin/arq). It focuses on providing at-least-once job processing semantics with features like automatic retries, job timeouts, dead-letter queues, and graceful worker shutdown.
34
36
 
35
- ## 🆕 What's New in v0.7.0
37
+ ## 🆕 What's New in v0.8.1
38
+
39
+ - **Distributed Tracing**: One-line integrations for Datadog, OpenTelemetry, and Logfire
40
+ - **Trace Context Propagation**: Automatic trace context from producer to worker
41
+ - **Prometheus & StatsD Exporters**: New metrics exporters for monitoring
42
+
43
+ ## What's New in v0.7
36
44
 
37
45
  - **Comprehensive CLI Tools**: 15+ new commands for monitoring, debugging, and management
38
46
  - **Real-time Monitoring Dashboard**: Interactive dashboard with `rrq monitor`
39
47
  - **Enhanced DLQ Management**: Sophisticated filtering and requeuing capabilities
40
- - **Python 3.10 Support**: Expanded compatibility from Python 3.11+ to 3.10+
41
48
  - **Bug Fixes**: Critical fix for unique job enqueue failures with proper deferral
42
49
 
43
50
  ## Requirements
44
51
 
45
- - Python 3.10 or higher
52
+ - Python 3.11 or higher
46
53
  - Redis 5.0 or higher
47
54
  - asyncio-compatible environment
48
55
 
@@ -144,6 +151,8 @@ this purpose.
144
151
 
145
152
  ```python
146
153
  # worker_script.py
154
+ import asyncio
155
+
147
156
  from rrq.worker import RRQWorker
148
157
  from config import rrq_settings # Import your settings
149
158
  from main_setup import job_registry # Import your registry
@@ -153,7 +162,7 @@ worker = RRQWorker(settings=rrq_settings, job_registry=job_registry)
153
162
 
154
163
  # Run the worker (blocking)
155
164
  if __name__ == "__main__":
156
- worker.run()
165
+ asyncio.run(worker.run())
157
166
  ```
158
167
 
159
168
  You can run multiple instances of `worker_script.py` for concurrent processing.
@@ -433,6 +442,41 @@ RRQ can be configured in several ways, with the following precedence:
433
442
 
434
443
  **Important Note on `job_registry`**: The `job_registry` attribute in your `RRQSettings` object is **critical** for RRQ to function. It must be an instance of `JobRegistry` and is used to register job handlers. Without a properly configured `job_registry`, workers will not know how to process jobs, and most operations will fail. Ensure it is set in your settings object to map job names to their respective handler functions.
435
444
 
445
+ ## Telemetry (Datadog / OTEL / Logfire)
446
+
447
+ RRQ supports optional distributed tracing for enqueue and job execution. Enable the
448
+ integration in both the producer and worker processes to get end-to-end traces
449
+ across the Redis queue.
450
+
451
+ ### Datadog (ddtrace)
452
+
453
+ ```python
454
+ from rrq.integrations.ddtrace import enable as enable_rrq_ddtrace
455
+
456
+ enable_rrq_ddtrace(service="myapp-rrq")
457
+ ```
458
+
459
+ This only instruments RRQ spans + propagation; it does **not** call
460
+ `ddtrace.patch_all()`. Configure `ddtrace` in your app as you already do.
461
+
462
+ ### Logfire
463
+
464
+ ```python
465
+ import logfire
466
+ from rrq.integrations.logfire import enable as enable_rrq_logfire
467
+
468
+ logfire.configure(service_name="myapp-rrq")
469
+ enable_rrq_logfire(service_name="myapp-rrq")
470
+ ```
471
+
472
+ ### OpenTelemetry (generic)
473
+
474
+ ```python
475
+ from rrq.integrations.otel import enable as enable_rrq_otel
476
+
477
+ enable_rrq_otel(service_name="myapp-rrq")
478
+ ```
479
+
436
480
  ### Comprehensive CLI Command System
437
481
  - **New modular CLI architecture** with dedicated command modules for better organization
438
482
  - **Enhanced monitoring capabilities** with real-time dashboards and beautiful table output
@@ -2,17 +2,22 @@
2
2
 
3
3
  RRQ is a Python library for creating reliable job queues using Redis and `asyncio`, inspired by [ARQ (Async Redis Queue)](https://github.com/samuelcolvin/arq). It focuses on providing at-least-once job processing semantics with features like automatic retries, job timeouts, dead-letter queues, and graceful worker shutdown.
4
4
 
5
- ## 🆕 What's New in v0.7.0
5
+ ## 🆕 What's New in v0.8.1
6
+
7
+ - **Distributed Tracing**: One-line integrations for Datadog, OpenTelemetry, and Logfire
8
+ - **Trace Context Propagation**: Automatic trace context from producer to worker
9
+ - **Prometheus & StatsD Exporters**: New metrics exporters for monitoring
10
+
11
+ ## What's New in v0.7
6
12
 
7
13
  - **Comprehensive CLI Tools**: 15+ new commands for monitoring, debugging, and management
8
14
  - **Real-time Monitoring Dashboard**: Interactive dashboard with `rrq monitor`
9
15
  - **Enhanced DLQ Management**: Sophisticated filtering and requeuing capabilities
10
- - **Python 3.10 Support**: Expanded compatibility from Python 3.11+ to 3.10+
11
16
  - **Bug Fixes**: Critical fix for unique job enqueue failures with proper deferral
12
17
 
13
18
  ## Requirements
14
19
 
15
- - Python 3.10 or higher
20
+ - Python 3.11 or higher
16
21
  - Redis 5.0 or higher
17
22
  - asyncio-compatible environment
18
23
 
@@ -114,6 +119,8 @@ this purpose.
114
119
 
115
120
  ```python
116
121
  # worker_script.py
122
+ import asyncio
123
+
117
124
  from rrq.worker import RRQWorker
118
125
  from config import rrq_settings # Import your settings
119
126
  from main_setup import job_registry # Import your registry
@@ -123,7 +130,7 @@ worker = RRQWorker(settings=rrq_settings, job_registry=job_registry)
123
130
 
124
131
  # Run the worker (blocking)
125
132
  if __name__ == "__main__":
126
- worker.run()
133
+ asyncio.run(worker.run())
127
134
  ```
128
135
 
129
136
  You can run multiple instances of `worker_script.py` for concurrent processing.
@@ -403,6 +410,41 @@ RRQ can be configured in several ways, with the following precedence:
403
410
 
404
411
  **Important Note on `job_registry`**: The `job_registry` attribute in your `RRQSettings` object is **critical** for RRQ to function. It must be an instance of `JobRegistry` and is used to register job handlers. Without a properly configured `job_registry`, workers will not know how to process jobs, and most operations will fail. Ensure it is set in your settings object to map job names to their respective handler functions.
405
412
 
413
+ ## Telemetry (Datadog / OTEL / Logfire)
414
+
415
+ RRQ supports optional distributed tracing for enqueue and job execution. Enable the
416
+ integration in both the producer and worker processes to get end-to-end traces
417
+ across the Redis queue.
418
+
419
+ ### Datadog (ddtrace)
420
+
421
+ ```python
422
+ from rrq.integrations.ddtrace import enable as enable_rrq_ddtrace
423
+
424
+ enable_rrq_ddtrace(service="myapp-rrq")
425
+ ```
426
+
427
+ This only instruments RRQ spans + propagation; it does **not** call
428
+ `ddtrace.patch_all()`. Configure `ddtrace` in your app as you already do.
429
+
430
+ ### Logfire
431
+
432
+ ```python
433
+ import logfire
434
+ from rrq.integrations.logfire import enable as enable_rrq_logfire
435
+
436
+ logfire.configure(service_name="myapp-rrq")
437
+ enable_rrq_logfire(service_name="myapp-rrq")
438
+ ```
439
+
440
+ ### OpenTelemetry (generic)
441
+
442
+ ```python
443
+ from rrq.integrations.otel import enable as enable_rrq_otel
444
+
445
+ enable_rrq_otel(service_name="myapp-rrq")
446
+ ```
447
+
406
448
  ### Comprehensive CLI Command System
407
449
  - **New modular CLI architecture** with dedicated command modules for better organization
408
450
  - **Enhanced monitoring capabilities** with real-time dashboards and beautiful table output
@@ -163,18 +163,18 @@ async def main():
163
163
  _queue_name="high_priority", # Example custom queue
164
164
  )
165
165
 
166
- if all([job1, job2, job3, job4, job5]):
167
- logger.info("Jobs enqueued successfully.")
168
- logger.info(f" - Job 1 (Success): {job1.id}")
169
- logger.info(f" - Job 2 (Failure): {job2.id}")
170
- logger.info(f" - Job 3 (Retry): {job3.id}")
171
- logger.info(f" - Job 4 (Deferred): {job4.id}")
172
- logger.info(f" - Job 5 (CustomQ): {job5.id}")
173
- else:
166
+ if job1 is None or job2 is None or job3 is None or job4 is None or job5 is None:
174
167
  logger.error("Some jobs failed to enqueue.")
175
168
  await client.close()
176
169
  return
177
170
 
171
+ logger.info("Jobs enqueued successfully.")
172
+ logger.info(f" - Job 1 (Success): {job1.id}")
173
+ logger.info(f" - Job 2 (Failure): {job2.id}")
174
+ logger.info(f" - Job 3 (Retry): {job3.id}")
175
+ logger.info(f" - Job 4 (Deferred): {job4.id}")
176
+ logger.info(f" - Job 5 (CustomQ): {job5.id}")
177
+
178
178
  await client.close() # Close client connection if no longer needed
179
179
 
180
180
  # 5. Worker Setup
@@ -247,17 +247,12 @@ async def main():
247
247
 
248
248
  async def run_worker_async(worker: RRQWorker):
249
249
  """Helper function to run the worker's main loop asynchronously."""
250
- # We don't use worker.run() here because it's synchronous.
251
- # Instead, we directly await the async _run_loop method.
252
250
  try:
253
- await worker._run_loop()
251
+ await worker.run()
254
252
  except Exception as e:
255
253
  logger.error(
256
- f"Worker {worker.worker_id} _run_loop exited with error: {e}", exc_info=True
254
+ f"Worker {worker.worker_id} run exited with error: {e}", exc_info=True
257
255
  )
258
- finally:
259
- # Ensure resources are closed even if _run_loop fails unexpectedly
260
- await worker._close_resources()
261
256
 
262
257
 
263
258
  if __name__ == "__main__":
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "rrq"
7
- version = "0.7.1"
7
+ version = "0.8.1"
8
8
  authors = [{ name = "Mazdak Rezvani", email = "mazdak@me.com" }]
9
9
  description = "RRQ is a Python library for creating reliable job queues using Redis and asyncio"
10
10
  readme = "README.md"
@@ -31,7 +31,13 @@ dependencies = [
31
31
  ]
32
32
 
33
33
  [project.optional-dependencies]
34
- dev = ["pytest>=8.3.5", "pytest-asyncio>=1.0.0", "pytest-cov>=6.0.0"]
34
+ dev = [
35
+ "pytest>=8.3.5",
36
+ "pytest-asyncio>=1.0.0",
37
+ "pytest-cov>=6.0.0",
38
+ "ruff==0.14.9",
39
+ "ty==0.0.1-alpha.26",
40
+ ]
35
41
 
36
42
  [project.urls]
37
43
  "Homepage" = "https://github.com/getresq/rrq"
@@ -43,9 +49,3 @@ rrq = "rrq.cli:rrq"
43
49
  [tool.pytest.ini_options]
44
50
  asyncio_mode = "strict"
45
51
  asyncio_default_fixture_loop_scope = "function"
46
-
47
- [tool.pyrefly]
48
- python_interpreter = ".venv/bin/python"
49
-
50
- [dependency-groups]
51
- dev = []
@@ -502,7 +502,8 @@ def _run_single_worker(
502
502
  """Helper function to run a single RRQ worker instance."""
503
503
  rrq_settings = _load_app_settings(settings_object_path)
504
504
 
505
- if not rrq_settings.job_registry:
505
+ job_registry = rrq_settings.job_registry
506
+ if job_registry is None:
506
507
  click.echo(
507
508
  click.style(
508
509
  "ERROR: No 'job_registry'. You must provide a JobRegistry instance in settings.",
@@ -511,15 +512,16 @@ def _run_single_worker(
511
512
  err=True,
512
513
  )
513
514
  sys.exit(1)
515
+ assert job_registry is not None
514
516
 
515
517
  logger.debug(
516
- f"Registered handlers (from effective registry): {rrq_settings.job_registry.get_registered_functions()}"
518
+ f"Registered handlers (from effective registry): {job_registry.get_registered_functions()}"
517
519
  )
518
520
  logger.debug(f"Effective RRQ settings for worker: {rrq_settings}")
519
521
 
520
522
  worker_instance = RRQWorker(
521
523
  settings=rrq_settings,
522
- job_registry=rrq_settings.job_registry,
524
+ job_registry=job_registry,
523
525
  queues=queues_arg,
524
526
  burst=burst,
525
527
  )
@@ -63,7 +63,10 @@ def auto_discover_commands(package_path: str) -> list[type[BaseCommand]]:
63
63
  # Get the package module
64
64
  try:
65
65
  package = importlib.import_module(package_path)
66
- package_dir = os.path.dirname(package.__file__)
66
+ package_file = package.__file__
67
+ if package_file is None:
68
+ return commands
69
+ package_dir = os.path.dirname(package_file)
67
70
  except ImportError:
68
71
  # Return empty list for non-existent packages
69
72
  return commands
@@ -452,7 +452,7 @@ class DebugCommands(AsyncCommand):
452
452
  console.print(f"Delay: {delay}s")
453
453
 
454
454
  finally:
455
- await client.aclose()
455
+ await client.close()
456
456
 
457
457
  async def _clear_data(
458
458
  self, settings_object_path: str, confirm: bool, pattern: str
@@ -548,4 +548,4 @@ class DebugCommands(AsyncCommand):
548
548
  console.print(f"\nStress test complete: {total_jobs} jobs submitted")
549
549
 
550
550
  finally:
551
- await client.aclose()
551
+ await client.close()