Skip to content

Commit 5f5acbe

Browse files
committed
Merge branch 'master' into allow-runtime-settings-overrides
2 parents f1e12d6 + f881e7a commit 5f5acbe

File tree

13 files changed

+216
-120
lines changed

13 files changed

+216
-120
lines changed

django_lightweight_queue/app_settings.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class Settings(Protocol):
3535
REDIS_HOST: str
3636
REDIS_PORT: int
3737
REDIS_PASSWORD: Optional[str]
38+
REDIS_DATABASE: int
3839
REDIS_PREFIX: str
3940

4041
ENABLE_PROMETHEUS: bool
@@ -57,6 +58,7 @@ class Defaults(Settings):
5758
REDIS_HOST = '127.0.0.1'
5859
REDIS_PORT = 6379
5960
REDIS_PASSWORD = None
61+
REDIS_DATABASE = 0
6062
REDIS_PREFIX = ""
6163

6264
ENABLE_PROMETHEUS = False

django_lightweight_queue/backends/base.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ def deduplicate(
5454
raise NotImplementedError()
5555

5656

57+
class BackendWithClear(BaseBackend, metaclass=ABCMeta):
58+
@abstractmethod
59+
def clear(self, queue: QueueName) -> None:
60+
raise NotImplementedError()
61+
62+
5763
class BackendWithPause(BaseBackend, metaclass=ABCMeta):
5864
@abstractmethod
5965
def pause(self, queue: QueueName, until: datetime.datetime) -> None:

django_lightweight_queue/backends/redis.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44
import redis
55

66
from ..job import Job
7-
from .base import BackendWithPauseResume
7+
from .base import BackendWithClear, BackendWithPauseResume
88
from ..types import QueueName, WorkerNumber
99
from ..utils import block_for_time
1010
from ..app_settings import app_settings
1111

1212

13-
class RedisBackend(BackendWithPauseResume):
13+
class RedisBackend(BackendWithPauseResume, BackendWithClear):
1414
"""
1515
This backend has at-most-once semantics.
1616
"""
@@ -20,6 +20,7 @@ def __init__(self) -> None:
2020
host=app_settings.REDIS_HOST,
2121
port=app_settings.REDIS_PORT,
2222
password=app_settings.REDIS_PASSWORD,
23+
db=app_settings.REDIS_DATABASE,
2324
)
2425

2526
def enqueue(self, job: Job, queue: QueueName) -> None:
@@ -78,6 +79,9 @@ def resume(self, queue: QueueName) -> None:
7879
def is_paused(self, queue: QueueName) -> bool:
7980
return bool(self.client.exists(self._pause_key(queue)))
8081

82+
def clear(self, queue: QueueName) -> None:
83+
self.client.delete(self._key(queue))
84+
8185
def _key(self, queue: QueueName) -> str:
8286
if app_settings.REDIS_PREFIX:
8387
return '{}:django_lightweight_queue:{}'.format(

django_lightweight_queue/backends/reliable_redis.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44
import redis
55

66
from ..job import Job
7-
from .base import BackendWithDeduplicate, BackendWithPauseResume
7+
from .base import (
8+
BackendWithClear,
9+
BackendWithDeduplicate,
10+
BackendWithPauseResume,
11+
)
812
from ..types import QueueName, WorkerNumber
913
from ..utils import block_for_time, get_worker_numbers
1014
from ..app_settings import app_settings
@@ -15,7 +19,7 @@
1519
T = TypeVar('T')
1620

1721

18-
class ReliableRedisBackend(BackendWithDeduplicate, BackendWithPauseResume):
22+
class ReliableRedisBackend(BackendWithClear, BackendWithDeduplicate, BackendWithPauseResume):
1923
"""
2024
This backend manages a per-queue-per-worker 'processing' queue. E.g. if we
2125
had a queue called 'django_lightweight_queue:things', and two workers, we
@@ -42,6 +46,7 @@ def __init__(self) -> None:
4246
host=app_settings.REDIS_HOST,
4347
port=app_settings.REDIS_PORT,
4448
password=app_settings.REDIS_PASSWORD,
49+
db=app_settings.REDIS_DATABASE,
4550
)
4651

4752
def startup(self, queue: QueueName) -> None:
@@ -228,6 +233,9 @@ def resume(self, queue: QueueName) -> None:
228233
def is_paused(self, queue: QueueName) -> bool:
229234
return bool(self.client.exists(self._pause_key(queue)))
230235

236+
def clear(self, queue: QueueName) -> None:
237+
self.client.delete(self._key(queue))
238+
231239
def _key(self, queue: QueueName) -> str:
232240
key = 'django_lightweight_queue:{}'.format(queue)
233241

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import warnings
2+
from typing import Any, Optional
3+
4+
from django.core.management.base import BaseCommand, CommandParser
5+
6+
from .utils import load_extra_settings
7+
from .constants import SETTING_NAME_PREFIX
8+
9+
10+
class CommandWithExtraSettings(BaseCommand):
11+
"""
12+
Base class for handling `--extra-settings`.
13+
14+
Derived classes must call `handle_extra_settings` at the top of their
15+
`handle` method. For example:
16+
17+
class Command(CommandWithExtraSettings):
18+
def handle(self, **options: Any) -> None:
19+
super().handle_extra_settings(**options)
20+
...
21+
"""
22+
23+
def add_arguments(self, parser: CommandParser) -> None:
24+
super().add_arguments(parser)
25+
26+
extra_settings_group = parser.add_mutually_exclusive_group()
27+
extra_settings_group.add_argument(
28+
'--config',
29+
action='store',
30+
default=None,
31+
help="The path to an additional django-style config file to load "
32+
"(this spelling is deprecated in favour of '--extra-settings')",
33+
)
34+
extra_settings_group.add_argument(
35+
'--extra-settings',
36+
action='store',
37+
default=None,
38+
help="The path to an additional django-style settings file to load. "
39+
f"{SETTING_NAME_PREFIX}* settings discovered in this file will "
40+
"override those from the default Django settings.",
41+
)
42+
43+
def handle_extra_settings(
44+
self,
45+
*,
46+
config: Optional[str] = None,
47+
extra_settings: Optional[str],
48+
**_: Any
49+
) -> Optional[str]:
50+
"""
51+
Load extra settings if there are any.
52+
53+
Returns the filename (if any) of the extra settings that have been loaded.
54+
"""
55+
56+
if config is not None:
57+
warnings.warn(
58+
"Use of '--config' is deprecated in favour of '--extra-settings'.",
59+
category=DeprecationWarning,
60+
)
61+
extra_settings = config
62+
63+
# Configuration overrides
64+
if extra_settings is not None:
65+
load_extra_settings(extra_settings)
66+
67+
return extra_settings
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import argparse
2+
3+
from django.core.management.base import BaseCommand, CommandError
4+
5+
from ...types import QueueName
6+
from ...utils import get_backend
7+
from ...backends.base import BackendWithClear
8+
9+
10+
class Command(BaseCommand):
11+
help = """
12+
Command to clear work on a redis-backed queue.
13+
14+
All pending jobs will be deleted from the queue. In flight jobs won't be
15+
affected.
16+
""" # noqa:A003 # inherited name
17+
18+
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
19+
parser.add_argument(
20+
'queue',
21+
action='store',
22+
help="The queue to pause.",
23+
)
24+
25+
parser.add_argument(
26+
'--yes',
27+
dest='skip_prompt',
28+
action='store_true',
29+
help="Skip confirmation prompt.",
30+
)
31+
32+
def handle(self, queue: QueueName, skip_prompt: bool = False, **options: object) -> None:
33+
34+
backend = get_backend(queue)
35+
36+
if not isinstance(backend, BackendWithClear):
37+
raise CommandError(
38+
"Configured backend '{}.{}' doesn't support clearing".format(
39+
type(backend).__module__,
40+
type(backend).__name__,
41+
),
42+
)
43+
44+
if not skip_prompt:
45+
prompt = "Clear all jobs from queue {}) [y/N] ".format(queue)
46+
choice = input(prompt).lower()
47+
48+
if choice != "y":
49+
raise CommandError("Aborting")
50+
51+
backend.clear(queue)

django_lightweight_queue/management/commands/queue_configuration.py

Lines changed: 4 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,14 @@
1-
import warnings
21
from typing import Any
32

4-
from django.core.management.base import BaseCommand, CommandParser
5-
6-
from ...utils import get_backend, get_queue_counts, load_extra_settings
7-
from ...constants import SETTING_NAME_PREFIX
3+
from ...utils import get_backend, get_queue_counts
84
from ...app_settings import app_settings
5+
from ...command_utils import CommandWithExtraSettings
96
from ...cron_scheduler import get_cron_config
107

118

12-
class Command(BaseCommand):
13-
def add_arguments(self, parser: CommandParser) -> None:
14-
extra_settings_group = parser.add_mutually_exclusive_group()
15-
extra_settings_group.add_argument(
16-
'--config',
17-
action='store',
18-
default=None,
19-
help="The path to an additional django-style config file to load "
20-
"(this spelling is deprecated in favour of '--extra-settings')",
21-
)
22-
extra_settings_group.add_argument(
23-
'--extra-settings',
24-
action='store',
25-
default=None,
26-
help="The path to an additional django-style settings file to load. "
27-
f"{SETTING_NAME_PREFIX}* settings discovered in this file will "
28-
"override those from the default Django settings.",
29-
)
30-
9+
class Command(CommandWithExtraSettings):
3110
def handle(self, **options: Any) -> None:
32-
extra_config = options.pop('config')
33-
if extra_config is not None:
34-
warnings.warn(
35-
"Use of '--config' is deprecated in favour of '--extra-settings'.",
36-
category=DeprecationWarning,
37-
)
38-
options['extra_settings'] = extra_config
39-
40-
# Configuration overrides
41-
extra_settings = options['extra_settings']
42-
if extra_settings is not None:
43-
load_extra_settings(extra_settings)
11+
super().handle_extra_settings(**options)
4412

4513
print("django-lightweight-queue")
4614
print("========================")

django_lightweight_queue/management/commands/queue_runner.py

Lines changed: 16 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,21 @@
1-
import warnings
21
from typing import Any, Dict, Optional
32

43
import daemonize
54

65
from django.apps import apps
7-
from django.core.management.base import (
8-
BaseCommand,
9-
CommandError,
10-
CommandParser,
11-
)
6+
from django.core.management.base import CommandError, CommandParser
127

138
from ...types import QueueName
14-
from ...utils import (
15-
get_logger,
16-
get_backend,
17-
get_middleware,
18-
load_extra_settings,
19-
)
9+
from ...utils import get_logger, get_backend, get_middleware
2010
from ...runner import runner
21-
from ...constants import SETTING_NAME_PREFIX
11+
from ...command_utils import CommandWithExtraSettings
2212
from ...machine_types import Machine, PooledMachine, DirectlyConfiguredMachine
2313

2414

25-
class Command(BaseCommand):
15+
class Command(CommandWithExtraSettings):
2616
def add_arguments(self, parser: CommandParser) -> None:
17+
super().add_arguments(parser)
18+
2719
parser.add_argument(
2820
'--pidfile',
2921
action='store',
@@ -58,22 +50,6 @@ def add_arguments(self, parser: CommandParser) -> None:
5850
default=None,
5951
help="Only run the given queue, useful for local debugging",
6052
)
61-
extra_settings_group = parser.add_mutually_exclusive_group()
62-
extra_settings_group.add_argument(
63-
'--config',
64-
action='store',
65-
default=None,
66-
help="The path to an additional django-style config file to load "
67-
"(this spelling is deprecated in favour of '--extra-settings')",
68-
)
69-
extra_settings_group.add_argument(
70-
'--extra-settings',
71-
action='store',
72-
default=None,
73-
help="The path to an additional django-style settings file to load. "
74-
f"{SETTING_NAME_PREFIX}* settings discovered in this file will "
75-
"override those from the default Django settings.",
76-
)
7753
parser.add_argument(
7854
'--exact-configuration',
7955
action='store_true',
@@ -83,19 +59,11 @@ def add_arguments(self, parser: CommandParser) -> None:
8359
" '--of'.",
8460
)
8561

86-
def validate_and_normalise(self, options: Dict[str, Any]) -> None:
87-
extra_config = options.pop('config')
88-
if extra_config is not None:
89-
warnings.warn(
90-
"Use of '--config' is deprecated in favour of '--extra-settings'.",
91-
category=DeprecationWarning,
92-
)
93-
options['extra_settings'] = extra_config
94-
62+
def validate_and_normalise(self, options: Dict[str, Any], had_extra_settings: bool) -> None:
9563
if options['exact_configuration']:
96-
if not options['extra_settings']:
64+
if not had_extra_settings:
9765
raise CommandError(
98-
"Must provide a value for '--config' when using "
66+
"Must provide a value for '--extra-settings' when using "
9967
"'--exact-configuration'.",
10068
)
10169

@@ -127,19 +95,19 @@ def validate_and_normalise(self, options: Dict[str, Any]) -> None:
12795
def handle(self, **options: Any) -> None:
12896
logger = get_logger('dlq.master')
12997

130-
self.validate_and_normalise(options)
98+
extra_settings = super().handle_extra_settings(**options)
99+
100+
self.validate_and_normalise(
101+
options,
102+
had_extra_settings=extra_settings is not None,
103+
)
131104

132105
def touch_filename(name: str) -> Optional[str]:
133106
try:
134107
return options['touchfile'] % name
135108
except TypeError:
136109
return None
137110

138-
# Configuration overrides
139-
extra_config = options['extra_settings']
140-
if extra_config is not None:
141-
load_extra_settings(extra_config)
142-
143111
logger.info("Starting queue master")
144112

145113
# Ensure children will be able to import our backend
@@ -164,7 +132,7 @@ def touch_filename(name: str) -> Optional[str]:
164132
)
165133

166134
def run() -> None:
167-
runner(touch_filename, machine, logger)
135+
runner(touch_filename, machine, logger, extra_settings)
168136

169137
# fork() only after we have started enough to catch failure, including
170138
# being able to write to our pidfile.

0 commit comments

Comments
 (0)