rrq 0.3.5__tar.gz → 0.3.6__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.
- {rrq-0.3.5 → rrq-0.3.6}/PKG-INFO +1 -1
- {rrq-0.3.5 → rrq-0.3.6}/pyproject.toml +1 -1
- {rrq-0.3.5 → rrq-0.3.6}/rrq/cli.py +30 -18
- {rrq-0.3.5 → rrq-0.3.6}/tests/test_cli.py +73 -6
- {rrq-0.3.5 → rrq-0.3.6}/uv.lock +1 -1
- {rrq-0.3.5 → rrq-0.3.6}/.coverage +0 -0
- {rrq-0.3.5 → rrq-0.3.6}/.gitignore +0 -0
- {rrq-0.3.5 → rrq-0.3.6}/FUTURE.md +0 -0
- {rrq-0.3.5 → rrq-0.3.6}/LICENSE +0 -0
- {rrq-0.3.5 → rrq-0.3.6}/MANIFEST.in +0 -0
- {rrq-0.3.5 → rrq-0.3.6}/README.md +0 -0
- {rrq-0.3.5 → rrq-0.3.6}/example/example_rrq_settings.py +0 -0
- {rrq-0.3.5 → rrq-0.3.6}/example/rrq_example.py +0 -0
- {rrq-0.3.5 → rrq-0.3.6}/rrq/__init__.py +0 -0
- {rrq-0.3.5 → rrq-0.3.6}/rrq/client.py +0 -0
- {rrq-0.3.5 → rrq-0.3.6}/rrq/constants.py +0 -0
- {rrq-0.3.5 → rrq-0.3.6}/rrq/exc.py +0 -0
- {rrq-0.3.5 → rrq-0.3.6}/rrq/job.py +0 -0
- {rrq-0.3.5 → rrq-0.3.6}/rrq/registry.py +0 -0
- {rrq-0.3.5 → rrq-0.3.6}/rrq/settings.py +0 -0
- {rrq-0.3.5 → rrq-0.3.6}/rrq/store.py +0 -0
- {rrq-0.3.5 → rrq-0.3.6}/rrq/worker.py +0 -0
- {rrq-0.3.5 → rrq-0.3.6}/tests/__init__.py +0 -0
- {rrq-0.3.5 → rrq-0.3.6}/tests/test_client.py +0 -0
- {rrq-0.3.5 → rrq-0.3.6}/tests/test_registry.py +0 -0
- {rrq-0.3.5 → rrq-0.3.6}/tests/test_store.py +0 -0
- {rrq-0.3.5 → rrq-0.3.6}/tests/test_worker.py +0 -0
{rrq-0.3.5 → rrq-0.3.6}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rrq
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.6
|
|
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
|
|
@@ -18,6 +18,14 @@ from .settings import RRQSettings
|
|
|
18
18
|
from .store import JobStore
|
|
19
19
|
from .worker import RRQWorker
|
|
20
20
|
|
|
21
|
+
# Attempt to import dotenv components for .env file loading
|
|
22
|
+
try:
|
|
23
|
+
from dotenv import find_dotenv, load_dotenv
|
|
24
|
+
|
|
25
|
+
DOTENV_AVAILABLE = True
|
|
26
|
+
except ImportError:
|
|
27
|
+
DOTENV_AVAILABLE = False
|
|
28
|
+
|
|
21
29
|
logger = logging.getLogger(__name__)
|
|
22
30
|
|
|
23
31
|
|
|
@@ -28,12 +36,21 @@ def _load_app_settings(settings_object_path: str | None = None) -> RRQSettings:
|
|
|
28
36
|
If the environment variable is not set, will create a default settings object.
|
|
29
37
|
RRQ Setting objects, automatically pick up ENVIRONMENT variables starting with RRQ_.
|
|
30
38
|
|
|
39
|
+
This function will also attempt to load a .env file if python-dotenv is installed
|
|
40
|
+
and a .env file is found. System environment variables take precedence over .env variables.
|
|
41
|
+
|
|
31
42
|
Args:
|
|
32
43
|
settings_object_path: A string representing the path to the settings object. (e.g. "myapp.worker_config.rrq_settings").
|
|
33
44
|
|
|
34
45
|
Returns:
|
|
35
46
|
The RRQSettings object.
|
|
36
47
|
"""
|
|
48
|
+
if DOTENV_AVAILABLE:
|
|
49
|
+
dotenv_path = find_dotenv(usecwd=True)
|
|
50
|
+
if dotenv_path:
|
|
51
|
+
logger.debug(f"Loading .env file at: {dotenv_path}...")
|
|
52
|
+
load_dotenv(dotenv_path=dotenv_path, override=False)
|
|
53
|
+
|
|
37
54
|
try:
|
|
38
55
|
if settings_object_path is None:
|
|
39
56
|
settings_object_path = os.getenv("RRQ_SETTINGS")
|
|
@@ -153,12 +170,10 @@ def start_rrq_worker_subprocess(
|
|
|
153
170
|
) -> subprocess.Popen | None:
|
|
154
171
|
"""Start an RRQ worker process, optionally for specific queues."""
|
|
155
172
|
command = ["rrq", "worker", "run"]
|
|
173
|
+
|
|
156
174
|
if settings_object_path:
|
|
157
175
|
command.extend(["--settings", settings_object_path])
|
|
158
|
-
|
|
159
|
-
raise ValueError(
|
|
160
|
-
"start_rrq_worker_subprocess called without settings_object_path!"
|
|
161
|
-
)
|
|
176
|
+
|
|
162
177
|
# Add queue filters if specified
|
|
163
178
|
if queues:
|
|
164
179
|
for q in queues:
|
|
@@ -220,16 +235,6 @@ async def watch_rrq_worker_impl(
|
|
|
220
235
|
settings_object_path: str | None = None,
|
|
221
236
|
queues: list[str] | None = None,
|
|
222
237
|
) -> None:
|
|
223
|
-
if not settings_object_path:
|
|
224
|
-
click.echo(
|
|
225
|
-
click.style(
|
|
226
|
-
"ERROR: 'rrq worker watch' requires --settings to be specified.",
|
|
227
|
-
fg="red",
|
|
228
|
-
),
|
|
229
|
-
err=True,
|
|
230
|
-
)
|
|
231
|
-
sys.exit(1)
|
|
232
|
-
|
|
233
238
|
abs_watch_path = os.path.abspath(watch_path)
|
|
234
239
|
click.echo(
|
|
235
240
|
f"Watching for file changes in {abs_watch_path} to restart RRQ worker (app settings: {settings_object_path})..."
|
|
@@ -295,7 +300,7 @@ def rrq():
|
|
|
295
300
|
"""RRQ: Reliable Redis Queue Command Line Interface.
|
|
296
301
|
|
|
297
302
|
Provides tools for running RRQ workers, checking system health,
|
|
298
|
-
and managing jobs. Requires an application-specific
|
|
303
|
+
and managing jobs. Requires an application-specific settings object
|
|
299
304
|
for most operations.
|
|
300
305
|
"""
|
|
301
306
|
pass
|
|
@@ -329,6 +334,7 @@ def worker_cli():
|
|
|
329
334
|
help=(
|
|
330
335
|
"Python settings path for application worker settings "
|
|
331
336
|
"(e.g., myapp.worker_config.rrq_settings). "
|
|
337
|
+
"Alternatively, this can be specified as RRQ_SETTINGS env variable. "
|
|
332
338
|
"The specified settings object must include a `job_registry: JobRegistry`."
|
|
333
339
|
),
|
|
334
340
|
)
|
|
@@ -337,7 +343,9 @@ def worker_run_command(
|
|
|
337
343
|
queues: tuple[str, ...],
|
|
338
344
|
settings_object_path: str,
|
|
339
345
|
):
|
|
340
|
-
"""Run an RRQ worker process.
|
|
346
|
+
"""Run an RRQ worker process.
|
|
347
|
+
Requires an application-specific settings object.
|
|
348
|
+
"""
|
|
341
349
|
rrq_settings = _load_app_settings(settings_object_path)
|
|
342
350
|
|
|
343
351
|
# Determine queues to poll
|
|
@@ -418,7 +426,9 @@ def worker_watch_command(
|
|
|
418
426
|
settings_object_path: str,
|
|
419
427
|
queues: tuple[str, ...],
|
|
420
428
|
):
|
|
421
|
-
"""Run the RRQ worker with auto-restart on file changes in PATH.
|
|
429
|
+
"""Run the RRQ worker with auto-restart on file changes in PATH.
|
|
430
|
+
Requires an application-specific settings object.
|
|
431
|
+
"""
|
|
422
432
|
# Run watch with optional queue filters
|
|
423
433
|
asyncio.run(
|
|
424
434
|
watch_rrq_worker_impl(
|
|
@@ -446,7 +456,9 @@ def worker_watch_command(
|
|
|
446
456
|
),
|
|
447
457
|
)
|
|
448
458
|
def check_command(settings_object_path: str):
|
|
449
|
-
"""Perform a health check on active RRQ worker(s).
|
|
459
|
+
"""Perform a health check on active RRQ worker(s).
|
|
460
|
+
Requires an application-specific settings object.
|
|
461
|
+
"""
|
|
450
462
|
click.echo("Performing RRQ health check...")
|
|
451
463
|
healthy = asyncio.run(
|
|
452
464
|
check_health_async_impl(settings_object_path=settings_object_path)
|
|
@@ -271,11 +271,21 @@ def test_worker_watch_command_with_queues(
|
|
|
271
271
|
assert kwargs.get("queues") == ["alpha", "beta"]
|
|
272
272
|
|
|
273
273
|
|
|
274
|
-
|
|
275
|
-
|
|
274
|
+
@mock.patch("rrq.cli.watch_rrq_worker_impl")
|
|
275
|
+
def test_worker_watch_command_missing_settings(mock_watch_impl, cli_runner):
|
|
276
|
+
"""Test 'rrq worker watch' without --settings uses default settings."""
|
|
277
|
+
async def dummy_watch_impl(path, settings_object_path=None, queues=None):
|
|
278
|
+
pass
|
|
279
|
+
|
|
280
|
+
mock_watch_impl.side_effect = dummy_watch_impl
|
|
281
|
+
|
|
276
282
|
result = cli_runner.invoke(cli.rrq, ["worker", "watch", "--path", "."])
|
|
277
|
-
assert result.exit_code
|
|
278
|
-
|
|
283
|
+
assert result.exit_code == 0
|
|
284
|
+
mock_watch_impl.assert_called_once()
|
|
285
|
+
args, kwargs = mock_watch_impl.call_args
|
|
286
|
+
assert args[0] == "." # Path argument
|
|
287
|
+
assert kwargs.get("settings_object_path") is None
|
|
288
|
+
assert kwargs.get("queues") is None
|
|
279
289
|
|
|
280
290
|
|
|
281
291
|
def test_worker_watch_command_invalid_path(cli_runner, mock_app_settings_path):
|
|
@@ -437,6 +447,63 @@ def test_load_app_settings_default(tmp_path, monkeypatch):
|
|
|
437
447
|
settings = _load_app_settings(None)
|
|
438
448
|
assert isinstance(settings, RRQSettings)
|
|
439
449
|
|
|
450
|
+
def test_load_app_settings_from_env_var(tmp_path, monkeypatch):
|
|
451
|
+
"""Test loading settings via RRQ_SETTINGS environment variable."""
|
|
452
|
+
# Create a fake module with a settings instance
|
|
453
|
+
module_dir = tmp_path / "env_mod"
|
|
454
|
+
module_dir.mkdir()
|
|
455
|
+
settings_file = module_dir / "settings_module.py"
|
|
456
|
+
settings_file.write_text(
|
|
457
|
+
"""
|
|
458
|
+
from rrq.settings import RRQSettings
|
|
459
|
+
from rrq.registry import JobRegistry
|
|
460
|
+
|
|
461
|
+
test_env_registry = JobRegistry()
|
|
462
|
+
test_env_settings = RRQSettings(redis_dsn="redis://envvar:333/7", job_registry=test_env_registry)
|
|
463
|
+
"""
|
|
464
|
+
)
|
|
465
|
+
# Ensure the new module path is discoverable
|
|
466
|
+
monkeypatch.syspath_prepend(str(tmp_path))
|
|
467
|
+
# Set the environment variable to point to our settings object
|
|
468
|
+
monkeypatch.setenv("RRQ_SETTINGS", "env_mod.settings_module.test_env_settings")
|
|
469
|
+
# Load settings without explicit argument
|
|
470
|
+
settings_object = _load_app_settings(None)
|
|
471
|
+
# Import the module to get the original instance for identity check
|
|
472
|
+
import importlib
|
|
473
|
+
imported_module = importlib.import_module("env_mod.settings_module")
|
|
474
|
+
assert settings_object is getattr(imported_module, "test_env_settings")
|
|
475
|
+
|
|
476
|
+
@pytest.mark.skipif(not cli.DOTENV_AVAILABLE, reason="python-dotenv not available")
|
|
477
|
+
def test_load_app_settings_from_dotenv(tmp_path, monkeypatch):
|
|
478
|
+
"""Test loading settings values from a .env file."""
|
|
479
|
+
# Ensure no pre-existing env var for redis_dsn or settings
|
|
480
|
+
monkeypatch.delenv("RRQ_REDIS_DSN", raising=False)
|
|
481
|
+
monkeypatch.delenv("RRQ_SETTINGS", raising=False)
|
|
482
|
+
# Create a .env file with a custom Redis DSN
|
|
483
|
+
env_file = tmp_path / ".env"
|
|
484
|
+
env_file.write_text("RRQ_REDIS_DSN=redis://dotenv:2222/2")
|
|
485
|
+
# Change CWD so find_dotenv will locate the .env file
|
|
486
|
+
monkeypatch.chdir(tmp_path)
|
|
487
|
+
# Load settings without explicit argument
|
|
488
|
+
settings_object = _load_app_settings(None)
|
|
489
|
+
# The redis_dsn should reflect the value from .env
|
|
490
|
+
assert settings_object.redis_dsn == "redis://dotenv:2222/2"
|
|
491
|
+
|
|
492
|
+
@pytest.mark.skipif(not cli.DOTENV_AVAILABLE, reason="python-dotenv not available")
|
|
493
|
+
def test_load_app_settings_dotenv_not_override_system_env(tmp_path, monkeypatch):
|
|
494
|
+
"""System environment variables should override .env file values."""
|
|
495
|
+
# Set system env var for redis_dsn
|
|
496
|
+
monkeypatch.setenv("RRQ_REDIS_DSN", "redis://env:1111/1")
|
|
497
|
+
monkeypatch.delenv("RRQ_SETTINGS", raising=False)
|
|
498
|
+
# Create a .env file with a different Redis DSN
|
|
499
|
+
env_file = tmp_path / ".env"
|
|
500
|
+
env_file.write_text("RRQ_REDIS_DSN=redis://dotenv:2222/2")
|
|
501
|
+
monkeypatch.chdir(tmp_path)
|
|
502
|
+
# Load settings without explicit argument
|
|
503
|
+
settings_object = _load_app_settings(None)
|
|
504
|
+
# Should use system env var, not .env value
|
|
505
|
+
assert settings_object.redis_dsn == "redis://env:1111/1"
|
|
506
|
+
|
|
440
507
|
|
|
441
508
|
def test_load_app_settings_invalid(monkeypatch, capsys):
|
|
442
509
|
# Invalid import path should exit with code 1
|
|
@@ -448,8 +515,8 @@ def test_load_app_settings_invalid(monkeypatch, capsys):
|
|
|
448
515
|
|
|
449
516
|
|
|
450
517
|
def test_start_rrq_worker_subprocess_no_settings():
|
|
451
|
-
# Calling without settings should
|
|
452
|
-
with pytest.raises(
|
|
518
|
+
# Calling without settings should attempt to start default worker process and fail if 'rrq' is not in PATH
|
|
519
|
+
with pytest.raises(FileNotFoundError):
|
|
453
520
|
start_rrq_worker_subprocess()
|
|
454
521
|
|
|
455
522
|
|
{rrq-0.3.5 → rrq-0.3.6}/uv.lock
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rrq-0.3.5 → rrq-0.3.6}/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|