django-periodic-tasks 0.1.0a3__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_periodic_tasks-0.1.0a3/.gitignore +224 -0
- django_periodic_tasks-0.1.0a3/.gitlab-ci.yml +59 -0
- django_periodic_tasks-0.1.0a3/.pre-commit-config.yaml +44 -0
- django_periodic_tasks-0.1.0a3/CHANGELOG.md +48 -0
- django_periodic_tasks-0.1.0a3/CLAUDE.md +50 -0
- django_periodic_tasks-0.1.0a3/Dockerfile +8 -0
- django_periodic_tasks-0.1.0a3/LICENSE +15 -0
- django_periodic_tasks-0.1.0a3/PKG-INFO +65 -0
- django_periodic_tasks-0.1.0a3/README.md +47 -0
- django_periodic_tasks-0.1.0a3/bin/publish.sh +22 -0
- django_periodic_tasks-0.1.0a3/django_periodic_tasks/__init__.py +15 -0
- django_periodic_tasks-0.1.0a3/django_periodic_tasks/admin.py +86 -0
- django_periodic_tasks-0.1.0a3/django_periodic_tasks/apps.py +39 -0
- django_periodic_tasks-0.1.0a3/django_periodic_tasks/cron.py +41 -0
- django_periodic_tasks-0.1.0a3/django_periodic_tasks/decorators.py +59 -0
- django_periodic_tasks-0.1.0a3/django_periodic_tasks/management/__init__.py +0 -0
- django_periodic_tasks-0.1.0a3/django_periodic_tasks/management/commands/__init__.py +0 -0
- django_periodic_tasks-0.1.0a3/django_periodic_tasks/management/commands/run_scheduler.py +60 -0
- django_periodic_tasks-0.1.0a3/django_periodic_tasks/migrations/0001_initial.py +37 -0
- django_periodic_tasks-0.1.0a3/django_periodic_tasks/migrations/0002_remove_redundant_next_run_at_db_index.py +17 -0
- django_periodic_tasks-0.1.0a3/django_periodic_tasks/migrations/0003_add_task_execution.py +31 -0
- django_periodic_tasks-0.1.0a3/django_periodic_tasks/migrations/__init__.py +0 -0
- django_periodic_tasks-0.1.0a3/django_periodic_tasks/models.py +146 -0
- django_periodic_tasks-0.1.0a3/django_periodic_tasks/registry.py +131 -0
- django_periodic_tasks-0.1.0a3/django_periodic_tasks/scheduler.py +200 -0
- django_periodic_tasks-0.1.0a3/django_periodic_tasks/sync.py +57 -0
- django_periodic_tasks-0.1.0a3/django_periodic_tasks/task_resolver.py +22 -0
- django_periodic_tasks-0.1.0a3/docker-compose.yml +12 -0
- django_periodic_tasks-0.1.0a3/docs/.pages +5 -0
- django_periodic_tasks-0.1.0a3/docs/api/.pages +6 -0
- django_periodic_tasks-0.1.0a3/docs/api/decorators.md +6 -0
- django_periodic_tasks-0.1.0a3/docs/api/index.md +3 -0
- django_periodic_tasks-0.1.0a3/docs/api/models.md +25 -0
- django_periodic_tasks-0.1.0a3/docs/api/registry.md +17 -0
- django_periodic_tasks-0.1.0a3/docs/api/settings.md +31 -0
- django_periodic_tasks-0.1.0a3/docs/guides/.pages +6 -0
- django_periodic_tasks-0.1.0a3/docs/guides/defining-schedules.md +179 -0
- django_periodic_tasks-0.1.0a3/docs/guides/exactly-once.md +87 -0
- django_periodic_tasks-0.1.0a3/docs/guides/getting-started.md +74 -0
- django_periodic_tasks-0.1.0a3/docs/guides/index.md +3 -0
- django_periodic_tasks-0.1.0a3/docs/guides/running.md +75 -0
- django_periodic_tasks-0.1.0a3/docs/index.md +43 -0
- django_periodic_tasks-0.1.0a3/docs/styles.css +58 -0
- django_periodic_tasks-0.1.0a3/mise.toml +44 -0
- django_periodic_tasks-0.1.0a3/mkdocs.yml +51 -0
- django_periodic_tasks-0.1.0a3/public/404.html +730 -0
- django_periodic_tasks-0.1.0a3/public/api/decorators.html +859 -0
- django_periodic_tasks-0.1.0a3/public/api/index.html +768 -0
- django_periodic_tasks-0.1.0a3/public/api/models.html +943 -0
- django_periodic_tasks-0.1.0a3/public/api/registry.html +949 -0
- django_periodic_tasks-0.1.0a3/public/api/settings.html +910 -0
- django_periodic_tasks-0.1.0a3/public/assets/images/favicon.png +0 -0
- django_periodic_tasks-0.1.0a3/public/assets/javascripts/bundle.79ae519e.min.js +16 -0
- django_periodic_tasks-0.1.0a3/public/assets/javascripts/bundle.79ae519e.min.js.map +7 -0
- django_periodic_tasks-0.1.0a3/public/assets/javascripts/lunr/min/lunr.ar.min.js +1 -0
- django_periodic_tasks-0.1.0a3/public/assets/javascripts/lunr/min/lunr.da.min.js +18 -0
- django_periodic_tasks-0.1.0a3/public/assets/javascripts/lunr/min/lunr.de.min.js +18 -0
- django_periodic_tasks-0.1.0a3/public/assets/javascripts/lunr/min/lunr.du.min.js +18 -0
- django_periodic_tasks-0.1.0a3/public/assets/javascripts/lunr/min/lunr.el.min.js +1 -0
- django_periodic_tasks-0.1.0a3/public/assets/javascripts/lunr/min/lunr.es.min.js +18 -0
- django_periodic_tasks-0.1.0a3/public/assets/javascripts/lunr/min/lunr.fi.min.js +18 -0
- django_periodic_tasks-0.1.0a3/public/assets/javascripts/lunr/min/lunr.fr.min.js +18 -0
- django_periodic_tasks-0.1.0a3/public/assets/javascripts/lunr/min/lunr.he.min.js +1 -0
- django_periodic_tasks-0.1.0a3/public/assets/javascripts/lunr/min/lunr.hi.min.js +1 -0
- django_periodic_tasks-0.1.0a3/public/assets/javascripts/lunr/min/lunr.hu.min.js +18 -0
- django_periodic_tasks-0.1.0a3/public/assets/javascripts/lunr/min/lunr.hy.min.js +1 -0
- django_periodic_tasks-0.1.0a3/public/assets/javascripts/lunr/min/lunr.it.min.js +18 -0
- django_periodic_tasks-0.1.0a3/public/assets/javascripts/lunr/min/lunr.ja.min.js +1 -0
- django_periodic_tasks-0.1.0a3/public/assets/javascripts/lunr/min/lunr.jp.min.js +1 -0
- django_periodic_tasks-0.1.0a3/public/assets/javascripts/lunr/min/lunr.kn.min.js +1 -0
- django_periodic_tasks-0.1.0a3/public/assets/javascripts/lunr/min/lunr.ko.min.js +1 -0
- django_periodic_tasks-0.1.0a3/public/assets/javascripts/lunr/min/lunr.multi.min.js +1 -0
- django_periodic_tasks-0.1.0a3/public/assets/javascripts/lunr/min/lunr.nl.min.js +18 -0
- django_periodic_tasks-0.1.0a3/public/assets/javascripts/lunr/min/lunr.no.min.js +18 -0
- django_periodic_tasks-0.1.0a3/public/assets/javascripts/lunr/min/lunr.pt.min.js +18 -0
- django_periodic_tasks-0.1.0a3/public/assets/javascripts/lunr/min/lunr.ro.min.js +18 -0
- django_periodic_tasks-0.1.0a3/public/assets/javascripts/lunr/min/lunr.ru.min.js +18 -0
- django_periodic_tasks-0.1.0a3/public/assets/javascripts/lunr/min/lunr.sa.min.js +1 -0
- django_periodic_tasks-0.1.0a3/public/assets/javascripts/lunr/min/lunr.stemmer.support.min.js +1 -0
- django_periodic_tasks-0.1.0a3/public/assets/javascripts/lunr/min/lunr.sv.min.js +18 -0
- django_periodic_tasks-0.1.0a3/public/assets/javascripts/lunr/min/lunr.ta.min.js +1 -0
- django_periodic_tasks-0.1.0a3/public/assets/javascripts/lunr/min/lunr.te.min.js +1 -0
- django_periodic_tasks-0.1.0a3/public/assets/javascripts/lunr/min/lunr.th.min.js +1 -0
- django_periodic_tasks-0.1.0a3/public/assets/javascripts/lunr/min/lunr.tr.min.js +18 -0
- django_periodic_tasks-0.1.0a3/public/assets/javascripts/lunr/min/lunr.vi.min.js +1 -0
- django_periodic_tasks-0.1.0a3/public/assets/javascripts/lunr/min/lunr.zh.min.js +1 -0
- django_periodic_tasks-0.1.0a3/public/assets/javascripts/lunr/tinyseg.js +206 -0
- django_periodic_tasks-0.1.0a3/public/assets/javascripts/lunr/wordcut.js +6708 -0
- django_periodic_tasks-0.1.0a3/public/assets/javascripts/workers/search.2c215733.min.js +42 -0
- django_periodic_tasks-0.1.0a3/public/assets/javascripts/workers/search.2c215733.min.js.map +7 -0
- django_periodic_tasks-0.1.0a3/public/assets/stylesheets/main.484c7ddc.min.css +1 -0
- django_periodic_tasks-0.1.0a3/public/assets/stylesheets/main.484c7ddc.min.css.map +1 -0
- django_periodic_tasks-0.1.0a3/public/assets/stylesheets/palette.ab4e12ef.min.css +1 -0
- django_periodic_tasks-0.1.0a3/public/assets/stylesheets/palette.ab4e12ef.min.css.map +1 -0
- django_periodic_tasks-0.1.0a3/public/guides/defining-schedules.html +1262 -0
- django_periodic_tasks-0.1.0a3/public/guides/exactly-once.html +1020 -0
- django_periodic_tasks-0.1.0a3/public/guides/getting-started.html +983 -0
- django_periodic_tasks-0.1.0a3/public/guides/index.html +768 -0
- django_periodic_tasks-0.1.0a3/public/guides/running.html +1047 -0
- django_periodic_tasks-0.1.0a3/public/index.html +913 -0
- django_periodic_tasks-0.1.0a3/public/search/search_index.json +1 -0
- django_periodic_tasks-0.1.0a3/public/sitemap.xml +3 -0
- django_periodic_tasks-0.1.0a3/public/sitemap.xml.gz +0 -0
- django_periodic_tasks-0.1.0a3/public/styles.css +58 -0
- django_periodic_tasks-0.1.0a3/pyproject.toml +125 -0
- django_periodic_tasks-0.1.0a3/renovate.json +4 -0
- django_periodic_tasks-0.1.0a3/sandbox/__init__.py +0 -0
- django_periodic_tasks-0.1.0a3/sandbox/manage.py +14 -0
- django_periodic_tasks-0.1.0a3/sandbox/settings.py +74 -0
- django_periodic_tasks-0.1.0a3/sandbox/settings_docgen.py +24 -0
- django_periodic_tasks-0.1.0a3/sandbox/testapp/__init__.py +0 -0
- django_periodic_tasks-0.1.0a3/sandbox/testapp/apps.py +6 -0
- django_periodic_tasks-0.1.0a3/sandbox/testapp/models.py +0 -0
- django_periodic_tasks-0.1.0a3/sandbox/testapp/tasks.py +19 -0
- django_periodic_tasks-0.1.0a3/sandbox/urls.py +6 -0
- django_periodic_tasks-0.1.0a3/tests/__init__.py +0 -0
- django_periodic_tasks-0.1.0a3/tests/test_admin.py +94 -0
- django_periodic_tasks-0.1.0a3/tests/test_apps.py +64 -0
- django_periodic_tasks-0.1.0a3/tests/test_commands.py +36 -0
- django_periodic_tasks-0.1.0a3/tests/test_cron.py +101 -0
- django_periodic_tasks-0.1.0a3/tests/test_decorators.py +113 -0
- django_periodic_tasks-0.1.0a3/tests/test_integration.py +279 -0
- django_periodic_tasks-0.1.0a3/tests/test_models.py +284 -0
- django_periodic_tasks-0.1.0a3/tests/test_registry.py +127 -0
- django_periodic_tasks-0.1.0a3/tests/test_scheduler.py +670 -0
- django_periodic_tasks-0.1.0a3/tests/test_sync.py +185 -0
- django_periodic_tasks-0.1.0a3/tests/test_task_resolver.py +32 -0
- django_periodic_tasks-0.1.0a3/tox.ini +17 -0
- django_periodic_tasks-0.1.0a3/uv.lock +1125 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# Created by https://www.toptal.com/developers/gitignore/api/python,linux,macos
|
|
2
|
+
# Edit at https://www.toptal.com/developers/gitignore?templates=python,linux,macos
|
|
3
|
+
|
|
4
|
+
### Linux ###
|
|
5
|
+
*~
|
|
6
|
+
|
|
7
|
+
# temporary files which can be created if a process still has a handle open of a deleted file
|
|
8
|
+
.fuse_hidden*
|
|
9
|
+
|
|
10
|
+
# KDE directory preferences
|
|
11
|
+
.directory
|
|
12
|
+
|
|
13
|
+
# Linux trash folder which might appear on any partition or disk
|
|
14
|
+
.Trash-*
|
|
15
|
+
|
|
16
|
+
# .nfs files are created when an open file is removed but is still being accessed
|
|
17
|
+
.nfs*
|
|
18
|
+
|
|
19
|
+
### macOS ###
|
|
20
|
+
# General
|
|
21
|
+
.DS_Store
|
|
22
|
+
.AppleDouble
|
|
23
|
+
.LSOverride
|
|
24
|
+
|
|
25
|
+
# Icon must end with two \r
|
|
26
|
+
Icon
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# Thumbnails
|
|
30
|
+
._*
|
|
31
|
+
|
|
32
|
+
# Files that might appear in the root of a volume
|
|
33
|
+
.DocumentRevisions-V100
|
|
34
|
+
.fseventsd
|
|
35
|
+
.Spotlight-V100
|
|
36
|
+
.TemporaryItems
|
|
37
|
+
.Trashes
|
|
38
|
+
.VolumeIcon.icns
|
|
39
|
+
.com.apple.timemachine.donotpresent
|
|
40
|
+
|
|
41
|
+
# Directories potentially created on remote AFP share
|
|
42
|
+
.AppleDB
|
|
43
|
+
.AppleDesktop
|
|
44
|
+
Network Trash Folder
|
|
45
|
+
Temporary Items
|
|
46
|
+
.apdisk
|
|
47
|
+
|
|
48
|
+
### macOS Patch ###
|
|
49
|
+
# iCloud generated files
|
|
50
|
+
*.icloud
|
|
51
|
+
|
|
52
|
+
### Python ###
|
|
53
|
+
# Byte-compiled / optimized / DLL files
|
|
54
|
+
__pycache__/
|
|
55
|
+
*.py[cod]
|
|
56
|
+
*$py.class
|
|
57
|
+
|
|
58
|
+
# C extensions
|
|
59
|
+
*.so
|
|
60
|
+
|
|
61
|
+
# Distribution / packaging
|
|
62
|
+
.Python
|
|
63
|
+
build/
|
|
64
|
+
develop-eggs/
|
|
65
|
+
dist/
|
|
66
|
+
downloads/
|
|
67
|
+
eggs/
|
|
68
|
+
.eggs/
|
|
69
|
+
lib/
|
|
70
|
+
lib64/
|
|
71
|
+
parts/
|
|
72
|
+
sdist/
|
|
73
|
+
var/
|
|
74
|
+
wheels/
|
|
75
|
+
share/python-wheels/
|
|
76
|
+
*.egg-info/
|
|
77
|
+
.installed.cfg
|
|
78
|
+
*.egg
|
|
79
|
+
MANIFEST
|
|
80
|
+
|
|
81
|
+
# PyInstaller
|
|
82
|
+
# Usually these files are written by a python script from a template
|
|
83
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
84
|
+
*.manifest
|
|
85
|
+
*.spec
|
|
86
|
+
|
|
87
|
+
# Installer logs
|
|
88
|
+
pip-log.txt
|
|
89
|
+
pip-delete-this-directory.txt
|
|
90
|
+
|
|
91
|
+
# Unit test / coverage reports
|
|
92
|
+
htmlcov/
|
|
93
|
+
.tox/
|
|
94
|
+
.nox/
|
|
95
|
+
.coverage
|
|
96
|
+
.coverage.*
|
|
97
|
+
.cache
|
|
98
|
+
nosetests.xml
|
|
99
|
+
coverage.xml
|
|
100
|
+
*.cover
|
|
101
|
+
*.py,cover
|
|
102
|
+
.hypothesis/
|
|
103
|
+
.pytest_cache/
|
|
104
|
+
cover/
|
|
105
|
+
|
|
106
|
+
# Translations
|
|
107
|
+
*.mo
|
|
108
|
+
*.pot
|
|
109
|
+
|
|
110
|
+
# Django stuff:
|
|
111
|
+
*.log
|
|
112
|
+
local_settings.py
|
|
113
|
+
db.sqlite3
|
|
114
|
+
db.sqlite3-journal
|
|
115
|
+
|
|
116
|
+
# Flask stuff:
|
|
117
|
+
instance/
|
|
118
|
+
.webassets-cache
|
|
119
|
+
|
|
120
|
+
# Scrapy stuff:
|
|
121
|
+
.scrapy
|
|
122
|
+
|
|
123
|
+
# Sphinx documentation
|
|
124
|
+
docs/_build/
|
|
125
|
+
|
|
126
|
+
# PyBuilder
|
|
127
|
+
.pybuilder/
|
|
128
|
+
target/
|
|
129
|
+
|
|
130
|
+
# Jupyter Notebook
|
|
131
|
+
.ipynb_checkpoints
|
|
132
|
+
|
|
133
|
+
# IPython
|
|
134
|
+
profile_default/
|
|
135
|
+
ipython_config.py
|
|
136
|
+
|
|
137
|
+
# pyenv
|
|
138
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
139
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
140
|
+
# .python-version
|
|
141
|
+
|
|
142
|
+
# pipenv
|
|
143
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
144
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
145
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
146
|
+
# install all needed dependencies.
|
|
147
|
+
#Pipfile.lock
|
|
148
|
+
|
|
149
|
+
# poetry
|
|
150
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
151
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
152
|
+
# commonly ignored for libraries.
|
|
153
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
154
|
+
#poetry.lock
|
|
155
|
+
|
|
156
|
+
# pdm
|
|
157
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
158
|
+
#pdm.lock
|
|
159
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
160
|
+
# in version control.
|
|
161
|
+
# https://pdm.fming.dev/#use-with-ide
|
|
162
|
+
.pdm.toml
|
|
163
|
+
|
|
164
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
165
|
+
__pypackages__/
|
|
166
|
+
|
|
167
|
+
# Celery stuff
|
|
168
|
+
celerybeat-schedule
|
|
169
|
+
celerybeat.pid
|
|
170
|
+
|
|
171
|
+
# SageMath parsed files
|
|
172
|
+
*.sage.py
|
|
173
|
+
|
|
174
|
+
# Environments
|
|
175
|
+
.env
|
|
176
|
+
.venv
|
|
177
|
+
env/
|
|
178
|
+
venv/
|
|
179
|
+
ENV/
|
|
180
|
+
env.bak/
|
|
181
|
+
venv.bak/
|
|
182
|
+
|
|
183
|
+
# Spyder project settings
|
|
184
|
+
.spyderproject
|
|
185
|
+
.spyproject
|
|
186
|
+
|
|
187
|
+
# Rope project settings
|
|
188
|
+
.ropeproject
|
|
189
|
+
|
|
190
|
+
# mkdocs documentation
|
|
191
|
+
/site
|
|
192
|
+
|
|
193
|
+
# mypy
|
|
194
|
+
.mypy_cache/
|
|
195
|
+
.dmypy.json
|
|
196
|
+
dmypy.json
|
|
197
|
+
|
|
198
|
+
# Pyre type checker
|
|
199
|
+
.pyre/
|
|
200
|
+
|
|
201
|
+
# pytype static type analyzer
|
|
202
|
+
.pytype/
|
|
203
|
+
|
|
204
|
+
# Cython debug symbols
|
|
205
|
+
cython_debug/
|
|
206
|
+
|
|
207
|
+
# PyCharm
|
|
208
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
209
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
210
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
211
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
212
|
+
#.idea/
|
|
213
|
+
|
|
214
|
+
### Python Patch ###
|
|
215
|
+
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
|
|
216
|
+
poetry.toml
|
|
217
|
+
|
|
218
|
+
# ruff
|
|
219
|
+
.ruff_cache/
|
|
220
|
+
|
|
221
|
+
# LSP config files
|
|
222
|
+
pyrightconfig.json
|
|
223
|
+
|
|
224
|
+
# End of https://www.toptal.com/developers/gitignore/api/python,linux,macos
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
workflow:
|
|
2
|
+
rules:
|
|
3
|
+
- if: $CI_COMMIT_REF_PROTECTED == "true"
|
|
4
|
+
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
|
5
|
+
|
|
6
|
+
stages:
|
|
7
|
+
- test
|
|
8
|
+
- docs
|
|
9
|
+
- release
|
|
10
|
+
|
|
11
|
+
cache:
|
|
12
|
+
key: "$CI_PROJECT_NAME"
|
|
13
|
+
paths:
|
|
14
|
+
- $HOME/.cache/pip
|
|
15
|
+
|
|
16
|
+
include:
|
|
17
|
+
- component: gitlab.com/thelabnyc/thelab-ci-components/review@0.7.1
|
|
18
|
+
- component: gitlab.com/thelabnyc/thelab-ci-components/precommit@0.7.1
|
|
19
|
+
- component: gitlab.com/thelabnyc/thelab-ci-components/publish-gitlab-release@0.7.1
|
|
20
|
+
- component: gitlab.com/thelabnyc/thelab-ci-components/publish-to-pypi@0.7.1
|
|
21
|
+
|
|
22
|
+
test:
|
|
23
|
+
stage: test
|
|
24
|
+
image: "registry.gitlab.com/thelabnyc/python:${IMAGE}"
|
|
25
|
+
variables:
|
|
26
|
+
POSTGRES_DB: postgres
|
|
27
|
+
POSTGRES_USER: postgres
|
|
28
|
+
POSTGRES_PASSWORD: ""
|
|
29
|
+
POSTGRES_HOST_AUTH_METHOD: "trust"
|
|
30
|
+
services:
|
|
31
|
+
- postgres:latest
|
|
32
|
+
script:
|
|
33
|
+
- uv sync --all-extras
|
|
34
|
+
- uv run tox
|
|
35
|
+
coverage: '/^TOTAL.+?(\d+\%)$/'
|
|
36
|
+
parallel:
|
|
37
|
+
matrix:
|
|
38
|
+
- IMAGE: "3.13"
|
|
39
|
+
TOX_SKIP_ENV: "^(?!py313-)"
|
|
40
|
+
- IMAGE: "3.14"
|
|
41
|
+
TOX_SKIP_ENV: "^(?!py314-)"
|
|
42
|
+
|
|
43
|
+
pages:
|
|
44
|
+
stage: docs
|
|
45
|
+
image: "registry.gitlab.com/thelabnyc/python:3.14"
|
|
46
|
+
rules:
|
|
47
|
+
- if: $CI_COMMIT_REF_PROTECTED == "true"
|
|
48
|
+
needs:
|
|
49
|
+
- test
|
|
50
|
+
variables:
|
|
51
|
+
DJANGO_SETTINGS_MODULE: sandbox.settings_docgen
|
|
52
|
+
script:
|
|
53
|
+
- uv sync --all-extras
|
|
54
|
+
- uv run mkdocs build --strict
|
|
55
|
+
- rm -rf ./public
|
|
56
|
+
- mv ./build/mkdocs ./public
|
|
57
|
+
artifacts:
|
|
58
|
+
paths:
|
|
59
|
+
- public
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://gitlab.com/thelabnyc/thelab-pre-commit-hooks
|
|
3
|
+
rev: v0.0.3
|
|
4
|
+
hooks:
|
|
5
|
+
- id: update-copyright-year
|
|
6
|
+
|
|
7
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
8
|
+
rev: v6.0.0
|
|
9
|
+
hooks:
|
|
10
|
+
- id: check-json
|
|
11
|
+
- id: check-merge-conflict
|
|
12
|
+
- id: check-symlinks
|
|
13
|
+
- id: check-toml
|
|
14
|
+
- id: check-yaml
|
|
15
|
+
args: [--unsafe]
|
|
16
|
+
- id: end-of-file-fixer
|
|
17
|
+
- id: trailing-whitespace
|
|
18
|
+
|
|
19
|
+
- repo: https://github.com/asottile/pyupgrade
|
|
20
|
+
rev: v3.21.2
|
|
21
|
+
hooks:
|
|
22
|
+
- id: pyupgrade
|
|
23
|
+
args: [--py313-plus]
|
|
24
|
+
|
|
25
|
+
- repo: https://github.com/adamchainz/django-upgrade
|
|
26
|
+
rev: "1.29.1"
|
|
27
|
+
hooks:
|
|
28
|
+
- id: django-upgrade
|
|
29
|
+
|
|
30
|
+
- repo: https://github.com/pycqa/isort
|
|
31
|
+
rev: "7.0.0"
|
|
32
|
+
hooks:
|
|
33
|
+
- id: isort
|
|
34
|
+
|
|
35
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
36
|
+
rev: v0.14.10
|
|
37
|
+
hooks:
|
|
38
|
+
- id: ruff
|
|
39
|
+
- id: ruff-format
|
|
40
|
+
|
|
41
|
+
- repo: https://github.com/commitizen-tools/commitizen
|
|
42
|
+
rev: v4.10.1
|
|
43
|
+
hooks:
|
|
44
|
+
- id: commitizen
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## v0.1.0a3 (2026-02-05)
|
|
9
|
+
|
|
10
|
+
## v0.1.0a2 (2026-02-05)
|
|
11
|
+
|
|
12
|
+
### Fix
|
|
13
|
+
|
|
14
|
+
- gitignored uv.lock
|
|
15
|
+
|
|
16
|
+
## v0.1.0a1 (2026-02-05)
|
|
17
|
+
|
|
18
|
+
## v0.1.0a0 (2026-02-05)
|
|
19
|
+
|
|
20
|
+
### Feat
|
|
21
|
+
|
|
22
|
+
- auto-discover tasks.py modules in installed apps
|
|
23
|
+
- add exactly-once docs, fluentcron examples, remove scheduler_db_worker
|
|
24
|
+
- add bug fixes and exactly-once decorator for scheduled tasks
|
|
25
|
+
- add MkDocs documentation with auto-generated API reference
|
|
26
|
+
- add integration tests
|
|
27
|
+
- add Django admin interface
|
|
28
|
+
- add management commands
|
|
29
|
+
- add PeriodicTaskScheduler with Docker test infrastructure
|
|
30
|
+
- add code-to-DB schedule sync
|
|
31
|
+
- add task path resolver
|
|
32
|
+
- add schedule registry and @scheduled_task decorator
|
|
33
|
+
- add ScheduledTask model
|
|
34
|
+
- add cron expression utilities
|
|
35
|
+
- initial project scaffolding
|
|
36
|
+
|
|
37
|
+
### Fix
|
|
38
|
+
|
|
39
|
+
- race condition in scheduler re-queuing
|
|
40
|
+
- uv commands
|
|
41
|
+
- remove dead code around TaskExec SKIPPED status
|
|
42
|
+
- hold row locks for entire tick to prevent duplicate task enqueue
|
|
43
|
+
|
|
44
|
+
### Refactor
|
|
45
|
+
|
|
46
|
+
- consolidate mocked tests and remove redundant test cases
|
|
47
|
+
- cleanup imports
|
|
48
|
+
- tweak how scheduler start works
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# django-periodic-tasks
|
|
2
|
+
|
|
3
|
+
Periodic/cron task scheduling for django-tasks. Backend-agnostic replacement for celery-beat.
|
|
4
|
+
|
|
5
|
+
## Project Structure
|
|
6
|
+
|
|
7
|
+
- `django_periodic_tasks/` - Main package
|
|
8
|
+
- `models.py` - ScheduledTask model
|
|
9
|
+
- `registry.py` - ScheduleRegistry, ScheduleEntry, @scheduled_task decorator
|
|
10
|
+
- `cron.py` - Cron expression utilities (croniter wrapper)
|
|
11
|
+
- `sync.py` - Code-to-DB sync
|
|
12
|
+
- `scheduler.py` - PeriodicTaskScheduler (daemon thread)
|
|
13
|
+
- `task_resolver.py` - Resolve task_path -> Task object
|
|
14
|
+
- `admin.py` - Django admin (read-only for code-defined tasks)
|
|
15
|
+
- `management/commands/` - run_scheduler
|
|
16
|
+
- `sandbox/` - Development/test Django project
|
|
17
|
+
- `tests/` - Test suite
|
|
18
|
+
|
|
19
|
+
## Development
|
|
20
|
+
|
|
21
|
+
Tests run inside Docker (PostgreSQL required for SELECT FOR UPDATE SKIP LOCKED):
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
mise run test # Run test suite
|
|
25
|
+
mise run mypy # Type checking
|
|
26
|
+
mise run coverage # Tests with coverage
|
|
27
|
+
mise run tox # Full tox matrix
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Or directly with docker compose:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
docker compose run --rm test uv run python sandbox/manage.py test --noinput -v 2 tests
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Settings
|
|
37
|
+
|
|
38
|
+
| Setting | Type | Default | Description |
|
|
39
|
+
|---------|------|---------|-------------|
|
|
40
|
+
| `PERIODIC_TASKS_AUTOSTART` | `bool` | `False` | Start scheduler daemon thread on `AppConfig.ready()` |
|
|
41
|
+
| `PERIODIC_TASKS_SCHEDULER_INTERVAL` | `int` | `15` | Seconds between scheduler ticks |
|
|
42
|
+
|
|
43
|
+
## Key Conventions
|
|
44
|
+
|
|
45
|
+
- Python 3.13+ required
|
|
46
|
+
- All code must pass mypy strict mode
|
|
47
|
+
- Tests use django-tasks DummyBackend with ENQUEUE_ON_COMMIT=False
|
|
48
|
+
- ScheduledTask.save() auto-computes next_run_at when it's None and task is enabled
|
|
49
|
+
- Code-defined schedules use source=CODE, admin-defined use source=DATABASE
|
|
50
|
+
- Scheduler uses SELECT FOR UPDATE SKIP LOCKED for safe multi-worker deployment
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025, thelab
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
10
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
11
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
12
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
13
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
14
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
15
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-periodic-tasks
|
|
3
|
+
Version: 0.1.0a3
|
|
4
|
+
Summary: Periodic/cron task scheduling for django-tasks. Backend-agnostic replacement for celery-beat.
|
|
5
|
+
Project-URL: Homepage, https://gitlab.com/thelabnyc/django-periodic-tasks
|
|
6
|
+
Project-URL: Repository, https://gitlab.com/thelabnyc/django-periodic-tasks
|
|
7
|
+
Author-email: thelab <thelabdev@thelab.co>
|
|
8
|
+
License: ISC
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Python: >=3.13
|
|
11
|
+
Requires-Dist: croniter>=1.0
|
|
12
|
+
Requires-Dist: django-tasks>=0.7
|
|
13
|
+
Requires-Dist: django>=5.2
|
|
14
|
+
Provides-Extra: rq
|
|
15
|
+
Requires-Dist: django-rq; extra == 'rq'
|
|
16
|
+
Requires-Dist: django-tasks[rq]; extra == 'rq'
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# django-periodic-tasks
|
|
20
|
+
|
|
21
|
+
Periodic/cron task scheduling for [django-tasks](https://github.com/RealOrangeOne/django-tasks). Backend-agnostic replacement for celery-beat + django-celery-beat.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install django-periodic-tasks
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Add to `INSTALLED_APPS`:
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
INSTALLED_APPS = [
|
|
33
|
+
...
|
|
34
|
+
"django_periodic_tasks",
|
|
35
|
+
]
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
### Define scheduled tasks
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from django_tasks import task
|
|
44
|
+
from django_periodic_tasks import scheduled_task
|
|
45
|
+
|
|
46
|
+
@scheduled_task(cron="0 5 * * *", name="daily-report")
|
|
47
|
+
@task()
|
|
48
|
+
def daily_report() -> None:
|
|
49
|
+
...
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Run the scheduler
|
|
53
|
+
|
|
54
|
+
Enable the autostart setting so the scheduler runs as a daemon thread inside your Django process:
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
# settings.py
|
|
58
|
+
PERIODIC_TASKS_AUTOSTART = True
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Or run it as a standalone process:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
python manage.py run_scheduler
|
|
65
|
+
```
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# django-periodic-tasks
|
|
2
|
+
|
|
3
|
+
Periodic/cron task scheduling for [django-tasks](https://github.com/RealOrangeOne/django-tasks). Backend-agnostic replacement for celery-beat + django-celery-beat.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install django-periodic-tasks
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Add to `INSTALLED_APPS`:
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
INSTALLED_APPS = [
|
|
15
|
+
...
|
|
16
|
+
"django_periodic_tasks",
|
|
17
|
+
]
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
### Define scheduled tasks
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
from django_tasks import task
|
|
26
|
+
from django_periodic_tasks import scheduled_task
|
|
27
|
+
|
|
28
|
+
@scheduled_task(cron="0 5 * * *", name="daily-report")
|
|
29
|
+
@task()
|
|
30
|
+
def daily_report() -> None:
|
|
31
|
+
...
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Run the scheduler
|
|
35
|
+
|
|
36
|
+
Enable the autostart setting so the scheduler runs as a daemon thread inside your Django process:
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
# settings.py
|
|
40
|
+
PERIODIC_TASKS_AUTOSTART = True
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Or run it as a standalone process:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
python manage.py run_scheduler
|
|
47
|
+
```
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -euxo pipefail
|
|
4
|
+
|
|
5
|
+
# Check git status
|
|
6
|
+
git fetch --all
|
|
7
|
+
CURRENT_BRANCH=$(git branch --show-current)
|
|
8
|
+
if [ "$CURRENT_BRANCH" != "master" ]; then
|
|
9
|
+
echo "This script must be run only when the master branch is checked out, but the current branch is ${CURRENT_BRANCH}. Abort!"
|
|
10
|
+
exit 1
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
NUM_BEHIND=$(git log ..origin/master | wc -l | awk '{print $1}')
|
|
14
|
+
if [ "$NUM_BEHIND" == "0" ]; then
|
|
15
|
+
echo ""
|
|
16
|
+
else
|
|
17
|
+
echo "Your branch is NOT up to date with origin/master. Abort! Please fetch and rebase first."
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
# Update version and publish via commitizen
|
|
22
|
+
cz bump "$@"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from django_periodic_tasks.decorators import exactly_once
|
|
2
|
+
from django_periodic_tasks.registry import (
|
|
3
|
+
ScheduleEntry,
|
|
4
|
+
ScheduleRegistry,
|
|
5
|
+
schedule_registry,
|
|
6
|
+
scheduled_task,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"ScheduleEntry",
|
|
11
|
+
"ScheduleRegistry",
|
|
12
|
+
"exactly_once",
|
|
13
|
+
"schedule_registry",
|
|
14
|
+
"scheduled_task",
|
|
15
|
+
]
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from django.contrib import admin
|
|
4
|
+
from django.http import HttpRequest
|
|
5
|
+
|
|
6
|
+
from django_periodic_tasks.models import ScheduledTask, TaskExecution
|
|
7
|
+
|
|
8
|
+
# Fields that are always read-only (computed/tracking)
|
|
9
|
+
TRACKING_READONLY = ("source", "last_run_at", "next_run_at", "total_run_count", "created_at", "updated_at")
|
|
10
|
+
|
|
11
|
+
# All editable model fields
|
|
12
|
+
ALL_FIELDS = (
|
|
13
|
+
"name",
|
|
14
|
+
"task_path",
|
|
15
|
+
"cron_expression",
|
|
16
|
+
"timezone",
|
|
17
|
+
"args",
|
|
18
|
+
"kwargs",
|
|
19
|
+
"source",
|
|
20
|
+
"enabled",
|
|
21
|
+
"queue_name",
|
|
22
|
+
"priority",
|
|
23
|
+
"backend",
|
|
24
|
+
"last_run_at",
|
|
25
|
+
"next_run_at",
|
|
26
|
+
"total_run_count",
|
|
27
|
+
"created_at",
|
|
28
|
+
"updated_at",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@admin.register(ScheduledTask)
|
|
33
|
+
class ScheduledTaskAdmin(admin.ModelAdmin[ScheduledTask]):
|
|
34
|
+
list_display = (
|
|
35
|
+
"name",
|
|
36
|
+
"task_path",
|
|
37
|
+
"cron_expression",
|
|
38
|
+
"source",
|
|
39
|
+
"enabled",
|
|
40
|
+
"last_run_at",
|
|
41
|
+
"next_run_at",
|
|
42
|
+
"total_run_count",
|
|
43
|
+
)
|
|
44
|
+
list_filter = ("source", "enabled")
|
|
45
|
+
search_fields = ("name", "task_path")
|
|
46
|
+
ordering = ("name",)
|
|
47
|
+
|
|
48
|
+
def get_readonly_fields(
|
|
49
|
+
self,
|
|
50
|
+
request: HttpRequest,
|
|
51
|
+
obj: ScheduledTask | None = None,
|
|
52
|
+
) -> tuple[str, ...]:
|
|
53
|
+
if obj is None:
|
|
54
|
+
# New task being created
|
|
55
|
+
return TRACKING_READONLY
|
|
56
|
+
if obj.source == ScheduledTask.Source.CODE:
|
|
57
|
+
# Code-defined tasks: everything is read-only
|
|
58
|
+
return ALL_FIELDS
|
|
59
|
+
# DB-defined tasks: only tracking fields are read-only
|
|
60
|
+
return TRACKING_READONLY
|
|
61
|
+
|
|
62
|
+
def has_delete_permission(
|
|
63
|
+
self,
|
|
64
|
+
request: HttpRequest,
|
|
65
|
+
obj: Any = None,
|
|
66
|
+
) -> bool:
|
|
67
|
+
if obj is not None and obj.source == ScheduledTask.Source.CODE:
|
|
68
|
+
return False
|
|
69
|
+
return super().has_delete_permission(request, obj)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@admin.register(TaskExecution)
|
|
73
|
+
class TaskExecutionAdmin(admin.ModelAdmin[TaskExecution]):
|
|
74
|
+
list_display = ("id", "scheduled_task", "status", "created_at", "completed_at")
|
|
75
|
+
list_filter = ("status",)
|
|
76
|
+
search_fields = ("scheduled_task__name",)
|
|
77
|
+
ordering = ("-created_at",)
|
|
78
|
+
|
|
79
|
+
def has_add_permission(self, request: HttpRequest) -> bool:
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
def has_change_permission(self, request: HttpRequest, obj: Any = None) -> bool:
|
|
83
|
+
return False
|
|
84
|
+
|
|
85
|
+
def has_delete_permission(self, request: HttpRequest, obj: Any = None) -> bool:
|
|
86
|
+
return False
|