django-crontask 1.1.2__tar.gz → 1.2.0__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.
- {django_crontask-1.1.2 → django_crontask-1.2.0}/PKG-INFO +14 -4
- {django_crontask-1.1.2 → django_crontask-1.2.0}/README.md +4 -1
- {django_crontask-1.1.2 → django_crontask-1.2.0}/crontask/__init__.py +10 -27
- django_crontask-1.2.0/crontask/_version.py +24 -0
- django_crontask-1.2.0/crontask/contrib/sentry.py +88 -0
- django_crontask-1.2.0/crontask/management/commands/__init__.py +0 -0
- {django_crontask-1.1.2 → django_crontask-1.2.0}/crontask/management/commands/crontask.py +6 -1
- {django_crontask-1.1.2 → django_crontask-1.2.0}/pyproject.toml +10 -2
- django_crontask-1.1.2/crontask/_version.py +0 -34
- {django_crontask-1.1.2 → django_crontask-1.2.0}/LICENSE +0 -0
- {django_crontask-1.1.2 → django_crontask-1.2.0}/crontask/conf.py +0 -0
- {django_crontask-1.1.2/crontask/management → django_crontask-1.2.0/crontask/contrib}/__init__.py +0 -0
- {django_crontask-1.1.2/crontask/management/commands → django_crontask-1.2.0/crontask/management}/__init__.py +0 -0
- {django_crontask-1.1.2 → django_crontask-1.2.0}/crontask/tasks.py +0 -0
- {django_crontask-1.1.2 → django_crontask-1.2.0}/crontask/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-crontask
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Cron style scheduler for Django's task framework.
|
|
5
5
|
Keywords: Django,cron,tasks,scheduler
|
|
6
6
|
Author-email: Rust Saiargaliev <fly.amureki@gmail.com>, Johannes Maron <johannes@maron.family>, Mostafa Mohamed <mostafa.anm91@gmail.com>, Jacqueline Kraus <jacquelinekraus1992@gmail.com>
|
|
@@ -11,7 +11,9 @@ Classifier: Programming Language :: Python
|
|
|
11
11
|
Classifier: Environment :: Web Environment
|
|
12
12
|
Classifier: License :: OSI Approved :: BSD License
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
14
|
-
Classifier: Operating System ::
|
|
14
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
15
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
16
|
+
Classifier: Operating System :: POSIX
|
|
15
17
|
Classifier: Topic :: Communications :: Email
|
|
16
18
|
Classifier: Topic :: Text Processing :: Markup :: Markdown
|
|
17
19
|
Classifier: Topic :: Software Development
|
|
@@ -29,7 +31,12 @@ Requires-Dist: django>=6.0
|
|
|
29
31
|
Requires-Dist: redis ; extra == "redis"
|
|
30
32
|
Requires-Dist: sentry-sdk ; extra == "sentry"
|
|
31
33
|
Project-URL: Changelog, https://github.com/codingjoe/django-crontask/releases
|
|
32
|
-
Project-URL:
|
|
34
|
+
Project-URL: Documentation, https://django-crontask.rtfd.io/
|
|
35
|
+
Project-URL: Funding, https://github.com/sponsors/codingjoe
|
|
36
|
+
Project-URL: Homepage, https://github.com/codingjoe/django-crontask
|
|
37
|
+
Project-URL: Issues, https://github.com/codingjoe/django-crontask/issues
|
|
38
|
+
Project-URL: Releasenotes, https://github.com/codingjoe/django-crontask/releases/latest
|
|
39
|
+
Project-URL: Source, https://github.com/codingjoe/django-crontask
|
|
33
40
|
Provides-Extra: redis
|
|
34
41
|
Provides-Extra: sentry
|
|
35
42
|
|
|
@@ -139,9 +146,12 @@ def my_interval_task():
|
|
|
139
146
|
|
|
140
147
|
### Sentry Cron Monitors
|
|
141
148
|
|
|
142
|
-
If you use [Sentry]
|
|
149
|
+
If you use [Sentry] suitable tasks will be automatically monitored.
|
|
143
150
|
The monitor's slug will be the actor's name. Like `my_task` in the example above.
|
|
144
151
|
|
|
152
|
+
Certain triggers are not supported by Sentry as well as sub-minute intervals.
|
|
153
|
+
In these cases, no monitor will be created and no telemetry will be sent.
|
|
154
|
+
|
|
145
155
|
### The crontask command
|
|
146
156
|
|
|
147
157
|
```ShellSession
|
|
@@ -104,9 +104,12 @@ def my_interval_task():
|
|
|
104
104
|
|
|
105
105
|
### Sentry Cron Monitors
|
|
106
106
|
|
|
107
|
-
If you use [Sentry]
|
|
107
|
+
If you use [Sentry] suitable tasks will be automatically monitored.
|
|
108
108
|
The monitor's slug will be the actor's name. Like `my_task` in the example above.
|
|
109
109
|
|
|
110
|
+
Certain triggers are not supported by Sentry as well as sub-minute intervals.
|
|
111
|
+
In these cases, no monitor will be created and no telemetry will be sent.
|
|
112
|
+
|
|
110
113
|
### The crontask command
|
|
111
114
|
|
|
112
115
|
```ShellSession
|
|
@@ -13,12 +13,13 @@ from django.tasks import Task
|
|
|
13
13
|
from django.utils import timezone
|
|
14
14
|
|
|
15
15
|
from . import _version
|
|
16
|
+
from .contrib import sentry
|
|
17
|
+
|
|
18
|
+
__all__ = ["cron", "interval", "scheduler", "VERSION", "__version__"]
|
|
16
19
|
|
|
17
20
|
__version__ = _version.version
|
|
18
21
|
VERSION = _version.version_tuple
|
|
19
22
|
|
|
20
|
-
__all__ = ["cron", "interval", "scheduler"]
|
|
21
|
-
|
|
22
23
|
|
|
23
24
|
class LazyBlockingScheduler(BlockingScheduler):
|
|
24
25
|
"""Avoid annoying info logs for pending jobs."""
|
|
@@ -45,21 +46,15 @@ def cron(schedule: str | BaseTrigger) -> typing.Callable[[Task], Task]:
|
|
|
45
46
|
def cron_test():
|
|
46
47
|
print("Cron test")
|
|
47
48
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
get any notifications if the cron job fails.
|
|
51
|
-
|
|
52
|
-
The monitor slug is your actor name, the schedule should be set to the same
|
|
53
|
-
cron schedule as the cron decorator. The schedule type should be set to cron.
|
|
54
|
-
The monitors timezone should be set to Europe/Berlin.
|
|
49
|
+
Sentry cron monitors are automatically upserted on every check-in
|
|
50
|
+
using the task name as the monitor slug.
|
|
55
51
|
"""
|
|
56
52
|
|
|
57
53
|
def decorator(task: Task) -> Task:
|
|
58
|
-
trigger = schedule
|
|
59
54
|
if isinstance(schedule, str):
|
|
60
55
|
*_, day_schedule = schedule.split(" ")
|
|
61
|
-
# CronTrigger uses Python's timezone
|
|
62
|
-
# so in Berlin
|
|
56
|
+
# CronTrigger uses Python's timezone-dependent first weekday,
|
|
57
|
+
# so in Berlin Monday is 0, and Sunday is 6. We use literals to avoid
|
|
63
58
|
# confusion. Literals are also more readable and crontab conform.
|
|
64
59
|
if any(i.isdigit() for i in day_schedule):
|
|
65
60
|
raise ValueError(
|
|
@@ -69,22 +64,10 @@ def cron(schedule: str | BaseTrigger) -> typing.Callable[[Task], Task]:
|
|
|
69
64
|
schedule,
|
|
70
65
|
timezone=timezone.get_default_timezone(),
|
|
71
66
|
)
|
|
72
|
-
|
|
73
|
-
try:
|
|
74
|
-
from sentry_sdk.crons import monitor
|
|
75
|
-
except ImportError:
|
|
76
|
-
fn = task.func
|
|
77
67
|
else:
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
task =
|
|
81
|
-
priority=task.priority,
|
|
82
|
-
func=fn,
|
|
83
|
-
queue_name=task.queue_name,
|
|
84
|
-
backend=task.backend,
|
|
85
|
-
takes_context=task.takes_context,
|
|
86
|
-
run_after=task.run_after,
|
|
87
|
-
)
|
|
68
|
+
trigger = schedule
|
|
69
|
+
|
|
70
|
+
task = sentry.monitor_cron_task(task, trigger)
|
|
88
71
|
|
|
89
72
|
scheduler.add_job(
|
|
90
73
|
func=task.enqueue,
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '1.2.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 2, 0)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = 'gae12a07e2'
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
|
|
3
|
+
from apscheduler.triggers.base import BaseTrigger
|
|
4
|
+
from apscheduler.triggers.calendarinterval import CalendarIntervalTrigger
|
|
5
|
+
from apscheduler.triggers.cron import CronTrigger
|
|
6
|
+
from apscheduler.triggers.interval import IntervalTrigger
|
|
7
|
+
from django.tasks import Task
|
|
8
|
+
from django.utils import timezone
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def trigger_to_monitor_config(trigger: BaseTrigger) -> dict[str, typing.Any] | None:
|
|
12
|
+
"""Convert an APScheduler trigger to a Sentry monitor configuration if possible."""
|
|
13
|
+
tz = str(timezone.get_default_timezone())
|
|
14
|
+
match trigger:
|
|
15
|
+
case CronTrigger():
|
|
16
|
+
fields = {f.name: str(f) for f in trigger.fields}
|
|
17
|
+
return {
|
|
18
|
+
"schedule": {
|
|
19
|
+
"type": "crontab",
|
|
20
|
+
"value": "{minute} {hour} {day} {month} {day_of_week}".format(
|
|
21
|
+
**fields
|
|
22
|
+
),
|
|
23
|
+
},
|
|
24
|
+
"timezone": tz,
|
|
25
|
+
}
|
|
26
|
+
case IntervalTrigger():
|
|
27
|
+
seconds = trigger.interval.total_seconds()
|
|
28
|
+
for unit, size in (
|
|
29
|
+
("year", 31536000),
|
|
30
|
+
("month", 2592000),
|
|
31
|
+
("week", 604800),
|
|
32
|
+
("day", 86400),
|
|
33
|
+
("hour", 3600),
|
|
34
|
+
("minute", 60),
|
|
35
|
+
):
|
|
36
|
+
if seconds % size == 0:
|
|
37
|
+
return {
|
|
38
|
+
"schedule": {
|
|
39
|
+
"type": "interval",
|
|
40
|
+
"value": seconds // size,
|
|
41
|
+
"unit": unit,
|
|
42
|
+
},
|
|
43
|
+
"timezone": tz,
|
|
44
|
+
}
|
|
45
|
+
return None # Less than a minute
|
|
46
|
+
case CalendarIntervalTrigger():
|
|
47
|
+
fields = {
|
|
48
|
+
"year": trigger.years,
|
|
49
|
+
"month": trigger.months,
|
|
50
|
+
"week": trigger.weeks,
|
|
51
|
+
"day": trigger.days,
|
|
52
|
+
}
|
|
53
|
+
set_fields = {k: v for k, v in fields.items() if v}
|
|
54
|
+
if len(set_fields) == 1:
|
|
55
|
+
((unit, value),) = set_fields.items()
|
|
56
|
+
return {
|
|
57
|
+
"schedule": {"type": "interval", "value": value, "unit": unit},
|
|
58
|
+
"timezone": tz,
|
|
59
|
+
}
|
|
60
|
+
return None
|
|
61
|
+
case _:
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def monitor_cron_task(task: Task, trigger: BaseTrigger) -> Task:
|
|
66
|
+
"""
|
|
67
|
+
Wrap the task function in a Sentry monitor for a suitable trigger.
|
|
68
|
+
|
|
69
|
+
You don't need to create the monitor in advance since
|
|
70
|
+
the Sentry monitor configuration is upserted on each task execution.
|
|
71
|
+
If the trigger is not supported, the task is returned unchanged.
|
|
72
|
+
"""
|
|
73
|
+
try:
|
|
74
|
+
from sentry_sdk import monitor
|
|
75
|
+
except ImportError:
|
|
76
|
+
return task
|
|
77
|
+
else:
|
|
78
|
+
if monitor_config := trigger_to_monitor_config(trigger):
|
|
79
|
+
return type(task)(
|
|
80
|
+
priority=task.priority,
|
|
81
|
+
func=monitor(task.name, monitor_config=monitor_config)(task.func),
|
|
82
|
+
queue_name=task.queue_name,
|
|
83
|
+
backend=task.backend,
|
|
84
|
+
takes_context=task.takes_context,
|
|
85
|
+
run_after=task.run_after,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
return task
|
|
File without changes
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import importlib
|
|
2
2
|
import signal
|
|
3
|
+
import sys
|
|
3
4
|
|
|
4
5
|
from apscheduler.triggers.interval import IntervalTrigger
|
|
5
6
|
from django.apps import apps
|
|
@@ -60,7 +61,11 @@ class Command(BaseCommand):
|
|
|
60
61
|
self.stderr.write("Another scheduler is already running.")
|
|
61
62
|
|
|
62
63
|
def launch_scheduler(self, lock, scheduler):
|
|
63
|
-
|
|
64
|
+
match sys.platform:
|
|
65
|
+
case "win32":
|
|
66
|
+
signal.signal(signal.SIGBREAK, kill_softly)
|
|
67
|
+
case _:
|
|
68
|
+
signal.signal(signal.SIGHUP, kill_softly)
|
|
64
69
|
signal.signal(signal.SIGTERM, kill_softly)
|
|
65
70
|
signal.signal(signal.SIGINT, kill_softly)
|
|
66
71
|
self.stdout.write(self.style.SUCCESS("Starting scheduler…"))
|
|
@@ -20,7 +20,9 @@ classifiers = [
|
|
|
20
20
|
"Environment :: Web Environment",
|
|
21
21
|
"License :: OSI Approved :: BSD License",
|
|
22
22
|
"Intended Audience :: Developers",
|
|
23
|
-
"Operating System ::
|
|
23
|
+
"Operating System :: Microsoft :: Windows",
|
|
24
|
+
"Operating System :: MacOS :: MacOS X",
|
|
25
|
+
"Operating System :: POSIX",
|
|
24
26
|
"Topic :: Communications :: Email",
|
|
25
27
|
"Topic :: Text Processing :: Markup :: Markdown",
|
|
26
28
|
"Topic :: Software Development",
|
|
@@ -41,8 +43,14 @@ sentry = ["sentry-sdk"]
|
|
|
41
43
|
redis = ["redis"]
|
|
42
44
|
|
|
43
45
|
[project.urls]
|
|
44
|
-
|
|
46
|
+
# https://packaging.python.org/en/latest/specifications/well-known-project-urls/#well-known-labels
|
|
47
|
+
Homepage = "https://github.com/codingjoe/django-crontask"
|
|
45
48
|
Changelog = "https://github.com/codingjoe/django-crontask/releases"
|
|
49
|
+
Source = "https://github.com/codingjoe/django-crontask"
|
|
50
|
+
Releasenotes = "https://github.com/codingjoe/django-crontask/releases/latest"
|
|
51
|
+
Documentation = "https://django-crontask.rtfd.io/"
|
|
52
|
+
Issues = "https://github.com/codingjoe/django-crontask/issues"
|
|
53
|
+
Funding = "https://github.com/sponsors/codingjoe"
|
|
46
54
|
|
|
47
55
|
[tool.flit.module]
|
|
48
56
|
name = "crontask"
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
# file generated by setuptools-scm
|
|
2
|
-
# don't change, don't track in version control
|
|
3
|
-
|
|
4
|
-
__all__ = [
|
|
5
|
-
"__version__",
|
|
6
|
-
"__version_tuple__",
|
|
7
|
-
"version",
|
|
8
|
-
"version_tuple",
|
|
9
|
-
"__commit_id__",
|
|
10
|
-
"commit_id",
|
|
11
|
-
]
|
|
12
|
-
|
|
13
|
-
TYPE_CHECKING = False
|
|
14
|
-
if TYPE_CHECKING:
|
|
15
|
-
from typing import Tuple
|
|
16
|
-
from typing import Union
|
|
17
|
-
|
|
18
|
-
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
19
|
-
COMMIT_ID = Union[str, None]
|
|
20
|
-
else:
|
|
21
|
-
VERSION_TUPLE = object
|
|
22
|
-
COMMIT_ID = object
|
|
23
|
-
|
|
24
|
-
version: str
|
|
25
|
-
__version__: str
|
|
26
|
-
__version_tuple__: VERSION_TUPLE
|
|
27
|
-
version_tuple: VERSION_TUPLE
|
|
28
|
-
commit_id: COMMIT_ID
|
|
29
|
-
__commit_id__: COMMIT_ID
|
|
30
|
-
|
|
31
|
-
__version__ = version = '1.1.2'
|
|
32
|
-
__version_tuple__ = version_tuple = (1, 1, 2)
|
|
33
|
-
|
|
34
|
-
__commit_id__ = commit_id = 'g08c90d2cc'
|
|
File without changes
|
|
File without changes
|
{django_crontask-1.1.2/crontask/management → django_crontask-1.2.0/crontask/contrib}/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|