mirror of
https://github.com/django-q2/django-q2.git
synced 2026-09-26 08:08:13 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c456cd0aa | ||
|
|
8f54e5d5ae | ||
|
|
689c881178 | ||
|
|
923c5daa73 | ||
|
|
69e794f1af | ||
|
|
2191897825 | ||
|
|
af505e8d70 | ||
|
|
48123af09d | ||
|
|
faaa57be93 | ||
|
|
5015d8ee4d | ||
|
|
cb253357fb | ||
|
|
efa0d6d9e4 | ||
|
|
ad4d24e17c | ||
|
|
1d1d0a9aa5 | ||
|
|
5e4aa65f62 |
@@ -1,2 +1,4 @@
|
|||||||
# flake8, black, isort
|
# flake8, black, isort
|
||||||
b1d000d007f3f77069719523268a0c6256dc0860
|
b1d000d007f3f77069719523268a0c6256dc0860
|
||||||
|
# move to ruff formatting/linting
|
||||||
|
ad4d24e17c9424b17cd8ae65c2def7ecc74e63c1
|
||||||
|
|||||||
+25
-20
@@ -7,29 +7,38 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
jobs:
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Lint with ruff
|
||||||
|
run: |
|
||||||
|
pipx install ruff==0.4.10
|
||||||
|
ruff format . --check && ruff check .
|
||||||
|
- name: Check twine
|
||||||
|
run: |
|
||||||
|
python -m pip install twine poetry rstcheck
|
||||||
|
poetry build
|
||||||
|
rstcheck README.rst
|
||||||
|
twine check dist/*
|
||||||
|
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: [ "3.8", "3.9", "3.10", "3.11", "3.12.0-rc.3" ]
|
python-version: [ "3.8", "3.9", "3.10", "3.11", "3.12" ]
|
||||||
django: [ "3.2", "4.1", "4.2", "5.0a1"]
|
django: [ "4.2", "5.0", "5.1"]
|
||||||
exclude:
|
exclude:
|
||||||
|
# django 5.1 does not support 3.8 and 3.9
|
||||||
|
- python-version: "3.8"
|
||||||
|
django: "5.1"
|
||||||
|
- python-version: "3.9"
|
||||||
|
django: "5.1"
|
||||||
# django 5.0 does not support 3.8 and 3.9
|
# django 5.0 does not support 3.8 and 3.9
|
||||||
- python-version: "3.8"
|
- python-version: "3.8"
|
||||||
django: "5.0a1"
|
django: "5.0"
|
||||||
- python-version: "3.9"
|
- python-version: "3.9"
|
||||||
django: "5.0a1"
|
django: "5.0"
|
||||||
# django 4.2 does not support 3.12
|
|
||||||
- python-version: "3.12.0-rc.3"
|
|
||||||
django: "4.2"
|
|
||||||
# django 4.1 does not support 3.12
|
|
||||||
- python-version: "3.12.0-rc.3"
|
|
||||||
django: "4.1"
|
|
||||||
# django 3.2 does not support 3.11 and 3.12
|
|
||||||
- python-version: "3.11"
|
|
||||||
django: "3.2"
|
|
||||||
- python-version: "3.12.0-rc.3"
|
|
||||||
django: "3.2"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
disque:
|
disque:
|
||||||
@@ -90,11 +99,7 @@ jobs:
|
|||||||
- name: Upload to coveralls
|
- name: Upload to coveralls
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
python -m pip install coveralls flake8 black
|
python -m pip install coveralls
|
||||||
coveralls --service=github --finish
|
coveralls --service=github --finish
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Check flake8/black
|
|
||||||
run: |
|
|
||||||
flake8 .
|
|
||||||
black --check .
|
|
||||||
|
|||||||
@@ -1,5 +1,29 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [v1.7.2](https://github.com/django-q2/django-q2/tree/v1.7.2) (2024-09-09)
|
||||||
|
|
||||||
|
- Fix twine check
|
||||||
|
|
||||||
|
## [v1.7.1](https://github.com/django-q2/django-q2/tree/v1.7.1) (2024-09-08)
|
||||||
|
|
||||||
|
- Fixed date of v1.7.0
|
||||||
|
- Fixed README.rst formatting which is blocking release of latest version
|
||||||
|
|
||||||
|
## [v1.7.0](https://github.com/django-q2/django-q2/tree/v1.7.0) (2024-09-08)
|
||||||
|
|
||||||
|
**Merged pull requests:**
|
||||||
|
|
||||||
|
- Remove support for Django 3.2 and 4.1 https://github.com/django-q2/django-q2/pull/183
|
||||||
|
- Fix max attempts for value 1 https://github.com/django-q2/django-q2/pull/185
|
||||||
|
- Replace black/isort with ruff https://github.com/django-q2/django-q2/pull/188
|
||||||
|
- Fix repeating task after timeout https://github.com/django-q2/django-q2/pull/184
|
||||||
|
- fix: Oracle ORM backend compatibility #180 https://github.com/django-q2/django-q2/pull/186
|
||||||
|
- chore: Update CI for Django 4.2 Python 3.12 support https://github.com/django-q2/django-q2/pull/208
|
||||||
|
- chore: Add Support Django 5.1 https://github.com/django-q2/django-q2/pull/207
|
||||||
|
- Call mark_process_dead on worker pid if prometheus_client is installed https://github.com/django-q2/django-q2/pull/212
|
||||||
|
- Add example project https://github.com/django-q2/django-q2/pull/215
|
||||||
|
- Add index on succeeded tasks https://github.com/django-q2/django-q2/pull/164
|
||||||
|
|
||||||
## [v1.6.2](https://github.com/django-q2/django-q2/tree/v1.6.2) (2024-03-05)
|
## [v1.6.2](https://github.com/django-q2/django-q2/tree/v1.6.2) (2024-03-05)
|
||||||
|
|
||||||
**Merged pull requests:**
|
**Merged pull requests:**
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
FROM python:3.9
|
FROM python:3.12
|
||||||
|
|
||||||
ENV PYTHONUNBUFFERED 1
|
ENV PYTHONUNBUFFERED 1
|
||||||
RUN mkdir -p /app
|
RUN mkdir -p /app
|
||||||
|
|||||||
+2
-2
@@ -8,10 +8,10 @@ ENV PYTHONUNBUFFERED 1
|
|||||||
ENV SHELL /bin/bash
|
ENV SHELL /bin/bash
|
||||||
|
|
||||||
# Upgrades pip
|
# Upgrades pip
|
||||||
RUN pip install --upgrade pip
|
RUN pip install -U pip setuptools
|
||||||
|
|
||||||
# Install poetry
|
# Install poetry
|
||||||
RUN pip install poetry
|
RUN pip install poetry==1.8.2
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
dev:
|
||||||
|
docker compose -f web-docker-compose.yaml up
|
||||||
|
|
||||||
|
test:
|
||||||
|
docker-compose -f test-services-docker-compose.yaml run --rm django-q2 poetry run pytest
|
||||||
|
|
||||||
|
shell:
|
||||||
|
docker-compose -f test-services-docker-compose.yaml run --rm django-q2 poetry run python manage.py shell
|
||||||
|
|
||||||
|
makemigrations:
|
||||||
|
docker-compose -f test-services-docker-compose.yaml run --rm django-q2 poetry run python manage.py makemigrations
|
||||||
|
|
||||||
|
migrate:
|
||||||
|
docker-compose -f test-services-docker-compose.yaml run --rm django-q2 poetry run python manage.py migrate
|
||||||
|
|
||||||
|
createsuperuser:
|
||||||
|
docker compose -f web-docker-compose.yaml run --rm web python manage.py createsuperuser
|
||||||
|
|
||||||
|
format:
|
||||||
|
docker compose -f test-services-docker-compose.yaml run --rm django-q2 poetry run ruff format .
|
||||||
|
docker compose -f test-services-docker-compose.yaml run --rm django-q2 poetry run ruff check . --fix
|
||||||
+21
-14
@@ -1,7 +1,7 @@
|
|||||||
A multiprocessing distributed task queue for Django
|
A multiprocessing distributed task queue for Django
|
||||||
---------------------------------------------------
|
---------------------------------------------------
|
||||||
|
|
||||||
|image0| |image1| |docs| |downloads|
|
|image0| |image1| |downloads|
|
||||||
|
|
||||||
Django Q2 is a fork of Django Q. Big thanks to Ilan Steemers for starting this project. Unfortunately, development has stalled since June 2021. Django Q2 is the new updated version of Django Q, with dependencies updates, docs updates and several bug fixes. Original repository: https://github.com/Koed00/django-q
|
Django Q2 is a fork of Django Q. Big thanks to Ilan Steemers for starting this project. Unfortunately, development has stalled since June 2021. Django Q2 is the new updated version of Django Q, with dependencies updates, docs updates and several bug fixes. Original repository: https://github.com/Koed00/django-q
|
||||||
|
|
||||||
@@ -35,10 +35,10 @@ See the `changelog <https://github.com/GDay/django-q2/blob/master/CHANGELOG.md>`
|
|||||||
Requirements
|
Requirements
|
||||||
~~~~~~~~~~~~
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
- `Django <https://www.djangoproject.com>`__ > = 3.2
|
- `Django <https://www.djangoproject.com>`__ > = 4.2
|
||||||
- `Django-picklefield <https://github.com/gintas/django-picklefield>`__
|
- `Django-picklefield <https://github.com/gintas/django-picklefield>`__
|
||||||
|
|
||||||
Tested with: Python 3.8, 3.9, 3.10, 3.11 and 3.12. Works with Django 3.2.X, 4.1.X, 4.2.X and 5.0.X
|
Tested with: Python 3.8, 3.9, 3.10, 3.11 and 3.12. Works with Django 4.2.X and 5.0.X
|
||||||
|
|
||||||
Brokers
|
Brokers
|
||||||
~~~~~~~
|
~~~~~~~
|
||||||
@@ -197,6 +197,23 @@ Admin page or directly from your code:
|
|||||||
|
|
||||||
For more info check the `Schedules <https://django-q2.readthedocs.org/en/latest/schedules.html>`__ documentation.
|
For more info check the `Schedules <https://django-q2.readthedocs.org/en/latest/schedules.html>`__ documentation.
|
||||||
|
|
||||||
|
Development
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
There is an example project that you can use to develop with. Docker (compose) is being used to set everything up.
|
||||||
|
Please note that you will have to restart the django-q container when changes have been made to tasks or django-q.
|
||||||
|
You can start the example project with:
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
make dev
|
||||||
|
|
||||||
|
Create a superuser with:
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
make createsuperuser
|
||||||
|
|
||||||
Testing
|
Testing
|
||||||
~~~~~~~
|
~~~~~~~
|
||||||
|
|
||||||
@@ -204,7 +221,7 @@ Running tests is easy with docker compose, it will also start the necessary data
|
|||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
docker-compose -f test-services-docker-compose.yaml run --rm django-q2 poetry run pytest
|
make test
|
||||||
|
|
||||||
Locale
|
Locale
|
||||||
~~~~~~
|
~~~~~~
|
||||||
@@ -212,12 +229,6 @@ Locale
|
|||||||
Currently available in English, German, Turkish, and French.
|
Currently available in English, German, Turkish, and French.
|
||||||
Translation pull requests are always welcome.
|
Translation pull requests are always welcome.
|
||||||
|
|
||||||
Todo
|
|
||||||
~~~~
|
|
||||||
|
|
||||||
- Better tests and coverage
|
|
||||||
- Less dependencies?
|
|
||||||
|
|
||||||
Acknowledgements
|
Acknowledgements
|
||||||
~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
@@ -234,9 +245,5 @@ Acknowledgements
|
|||||||
:target: https://github.com/GDay/django-q2/actions?query=workflow%3Atests
|
:target: https://github.com/GDay/django-q2/actions?query=workflow%3Atests
|
||||||
.. |image1| image:: https://coveralls.io/repos/github/GDay/django-q2/badge.svg?branch=master
|
.. |image1| image:: https://coveralls.io/repos/github/GDay/django-q2/badge.svg?branch=master
|
||||||
:target: https://coveralls.io/github/GDay/django-q2?branch=master
|
:target: https://coveralls.io/github/GDay/django-q2?branch=master
|
||||||
.. |docs| image:: https://readthedocs.org/projects/docs/badge/?version=latest
|
|
||||||
:alt: Documentation Status
|
|
||||||
:scale: 100
|
|
||||||
:target: https://django-q2.readthedocs.org/
|
|
||||||
.. |downloads| image:: https://img.shields.io/pypi/dm/django-q2
|
.. |downloads| image:: https://img.shields.io/pypi/dm/django-q2
|
||||||
:target: https://img.shields.io/pypi/dm/django-q2
|
:target: https://img.shields.io/pypi/dm/django-q2
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import django
|
import django
|
||||||
|
|
||||||
VERSION = (1, 6, 2)
|
VERSION = (1, 7, 2)
|
||||||
|
|
||||||
if django.VERSION < (3, 2):
|
if django.VERSION < (3, 2):
|
||||||
default_app_config = "django_q.apps.DjangoQConfig"
|
default_app_config = "django_q.apps.DjangoQConfig"
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
"""Admin module for Django."""
|
"""Admin module for Django."""
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.db.models.expressions import OuterRef, Subquery
|
from django.db.models.expressions import OuterRef, Subquery
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ def get_broker(list_key: str = None) -> Broker:
|
|||||||
|
|
||||||
return ironmq.IronMQBroker(list_key=list_key)
|
return ironmq.IronMQBroker(list_key=list_key)
|
||||||
# SQS
|
# SQS
|
||||||
elif type(Conf.SQS) == dict:
|
elif isinstance(Conf.SQS, dict):
|
||||||
from django_q.brokers import aws_sqs
|
from django_q.brokers import aws_sqs
|
||||||
|
|
||||||
return aws_sqs.Sqs(list_key=list_key)
|
return aws_sqs.Sqs(list_key=list_key)
|
||||||
|
|||||||
+10
-1
@@ -21,7 +21,14 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
|
|
||||||
# Local
|
# Local
|
||||||
from django_q.brokers import Broker, get_broker
|
from django_q.brokers import Broker, get_broker
|
||||||
from django_q.conf import Conf, get_ppid, logger, psutil, setproctitle
|
from django_q.conf import (
|
||||||
|
Conf,
|
||||||
|
get_ppid,
|
||||||
|
logger,
|
||||||
|
prometheus_multiprocess,
|
||||||
|
psutil,
|
||||||
|
setproctitle,
|
||||||
|
)
|
||||||
from django_q.humanhash import humanize
|
from django_q.humanhash import humanize
|
||||||
from django_q.monitor import monitor
|
from django_q.monitor import monitor
|
||||||
from django_q.pusher import pusher
|
from django_q.pusher import pusher
|
||||||
@@ -223,6 +230,8 @@ class Sentinel:
|
|||||||
% {"name": process.name}
|
% {"name": process.name}
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
if prometheus_multiprocess:
|
||||||
|
prometheus_multiprocess.mark_process_dead(process.pid)
|
||||||
self.pool.remove(process)
|
self.pool.remove(process)
|
||||||
self.spawn_worker()
|
self.spawn_worker()
|
||||||
if process.timer.value == 0:
|
if process.timer.value == 0:
|
||||||
|
|||||||
+5
-1
@@ -38,6 +38,11 @@ try:
|
|||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
setproctitle = None
|
setproctitle = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
from prometheus_client import multiprocess as prometheus_multiprocess
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
prometheus_multiprocess = None
|
||||||
|
|
||||||
|
|
||||||
class Conf:
|
class Conf:
|
||||||
"""
|
"""
|
||||||
@@ -263,7 +268,6 @@ if not logger.hasHandlers():
|
|||||||
|
|
||||||
# Error Reporting Interface
|
# Error Reporting Interface
|
||||||
class ErrorReporter:
|
class ErrorReporter:
|
||||||
|
|
||||||
# initialize with iterator of reporters (better name, targets?)
|
# initialize with iterator of reporters (better name, targets?)
|
||||||
def __init__(self, reporters):
|
def __init__(self, reporters):
|
||||||
self.targets = [target for target in reporters]
|
self.targets = [target for target in reporters]
|
||||||
|
|||||||
@@ -2,17 +2,22 @@ import datetime
|
|||||||
import time
|
import time
|
||||||
import zlib
|
import zlib
|
||||||
|
|
||||||
import django
|
from django.core.signing import (
|
||||||
from django.core.signing import BadSignature, JSONSerializer, SignatureExpired
|
BadSignature,
|
||||||
|
JSONSerializer,
|
||||||
|
SignatureExpired,
|
||||||
|
b64_decode,
|
||||||
|
dumps,
|
||||||
|
)
|
||||||
from django.core.signing import Signer as Sgnr
|
from django.core.signing import Signer as Sgnr
|
||||||
from django.core.signing import TimestampSigner as TsS
|
from django.core.signing import TimestampSigner as TsS
|
||||||
from django.core.signing import b64_decode, dumps
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from django.core.signing import b62_decode
|
from django.core.signing import b62_decode
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# fallback for django 3.x
|
# fallback for django 3.x
|
||||||
from django.utils.baseconv import base62
|
from django.utils.baseconv import base62
|
||||||
|
|
||||||
b62_decode = base62.decode
|
b62_decode = base62.decode
|
||||||
|
|
||||||
from django.utils.crypto import constant_time_compare
|
from django.utils.crypto import constant_time_compare
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
class TimeoutException(SystemExit):
|
||||||
|
"""
|
||||||
|
Exception for when a worker takes too long to complete a task
|
||||||
|
Raising SystemExit will make sure the function terminates gracefully.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
@@ -4,6 +4,7 @@ humanhash: Human-readable representations of digests.
|
|||||||
The simplest ways to use this module are the :func:`humanize` and :func:`uuid`
|
The simplest ways to use this module are the :func:`humanize` and :func:`uuid`
|
||||||
functions. For tighter control over the output, see :class:`HumanHasher`.
|
functions. For tighter control over the output, see :class:`HumanHasher`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import operator
|
import operator
|
||||||
import uuid as uuidlib
|
import uuid as uuidlib
|
||||||
from argparse import ArgumentError
|
from argparse import ArgumentError
|
||||||
@@ -270,7 +271,6 @@ DEFAULT_WORDLIST = (
|
|||||||
|
|
||||||
|
|
||||||
class HumanHasher:
|
class HumanHasher:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Transforms hex digests to human-readable strings.
|
Transforms hex digests to human-readable strings.
|
||||||
|
|
||||||
@@ -290,7 +290,6 @@ class HumanHasher:
|
|||||||
self.wordlist = wordlist
|
self.wordlist = wordlist
|
||||||
|
|
||||||
def humanize(self, hexdigest, words=4, separator="-"):
|
def humanize(self, hexdigest, words=4, separator="-"):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Humanize a given hexadecimal digest.
|
Humanize a given hexadecimal digest.
|
||||||
|
|
||||||
@@ -314,7 +313,6 @@ class HumanHasher:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def compress(bytes, target):
|
def compress(bytes, target):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Compress a list of byte values to a fixed target length.
|
Compress a list of byte values to a fixed target length.
|
||||||
|
|
||||||
@@ -353,7 +351,6 @@ class HumanHasher:
|
|||||||
return checksums
|
return checksums
|
||||||
|
|
||||||
def uuid(self, **params):
|
def uuid(self, **params):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Generate a UUID with a human-readable representation.
|
Generate a UUID with a human-readable representation.
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = []
|
dependencies = []
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("django_q", "0001_initial"),
|
("django_q", "0001_initial"),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("django_q", "0002_auto_20150630_1624"),
|
("django_q", "0002_auto_20150630_1624"),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from django.db import migrations
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("django_q", "0003_auto_20150708_1326"),
|
("django_q", "0003_auto_20150708_1326"),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("django_q", "0004_auto_20150710_1043"),
|
("django_q", "0004_auto_20150710_1043"),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("django_q", "0005_auto_20150718_1506"),
|
("django_q", "0005_auto_20150718_1506"),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("django_q", "0006_auto_20150805_1817"),
|
("django_q", "0006_auto_20150805_1817"),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("django_q", "0007_ormq"),
|
("django_q", "0007_ormq"),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("django_q", "0008_auto_20160224_1026"),
|
("django_q", "0008_auto_20160224_1026"),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ from django.db import migrations
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("django_q", "0009_auto_20171009_0915"),
|
("django_q", "0009_auto_20171009_0915"),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("django_q", "0010_auto_20200610_0856"),
|
("django_q", "0010_auto_20200610_0856"),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import django_q.models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("django_q", "0011_auto_20200628_1055"),
|
("django_q", "0011_auto_20200628_1055"),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("django_q", "0012_auto_20200702_1608"),
|
("django_q", "0012_auto_20200702_1608"),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("django_q", "0013_task_attempt_count"),
|
("django_q", "0013_task_attempt_count"),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("django_q", "0014_schedule_cluster"),
|
("django_q", "0014_schedule_cluster"),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import django_q.models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("django_q", "0015_alter_schedule_schedule_type"),
|
("django_q", "0015_alter_schedule_schedule_type"),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("django_q", "0016_schedule_intended_date_kwarg"),
|
("django_q", "0016_schedule_intended_date_kwarg"),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Generated by Django 4.2.7 on 2024-03-05 17:03
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("django_q", "0017_task_cluster_alter"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddIndex(
|
||||||
|
model_name="task",
|
||||||
|
index=models.Index(
|
||||||
|
condition=models.Q(("success", True)),
|
||||||
|
fields=["group", "name", "func"],
|
||||||
|
name="success_index",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
+9
-2
@@ -5,12 +5,12 @@ from keyword import iskeyword
|
|||||||
from django import get_version
|
from django import get_version
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.db.models import Q
|
||||||
from django.template.defaultfilters import truncatechars
|
from django.template.defaultfilters import truncatechars
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
from django.utils.timezone import is_aware
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
# External
|
# External
|
||||||
@@ -18,7 +18,7 @@ from picklefield import PickledObjectField
|
|||||||
from picklefield.fields import dbsafe_decode
|
from picklefield.fields import dbsafe_decode
|
||||||
|
|
||||||
# Local
|
# Local
|
||||||
from django_q.conf import Conf, croniter
|
from django_q.conf import croniter
|
||||||
from django_q.signing import SignedPackage
|
from django_q.signing import SignedPackage
|
||||||
from django_q.utils import add_months, add_years, localtime
|
from django_q.utils import add_months, add_years, localtime
|
||||||
|
|
||||||
@@ -112,6 +112,13 @@ class Task(models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
app_label = "django_q"
|
app_label = "django_q"
|
||||||
ordering = ["-stopped"]
|
ordering = ["-stopped"]
|
||||||
|
indexes = [
|
||||||
|
models.Index(
|
||||||
|
name="success_index",
|
||||||
|
fields=["group", "name", "func"],
|
||||||
|
condition=Q(success=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class SuccessManager(models.Manager):
|
class SuccessManager(models.Manager):
|
||||||
|
|||||||
+15
-17
@@ -2,8 +2,8 @@ from multiprocessing.process import current_process
|
|||||||
from multiprocessing.queues import Queue
|
from multiprocessing.queues import Queue
|
||||||
|
|
||||||
from django import core, db
|
from django import core, db
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
from django.apps.registry import apps
|
from django.apps.registry import apps
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
try:
|
try:
|
||||||
apps.check_apps_ready()
|
apps.check_apps_ready()
|
||||||
@@ -108,34 +108,28 @@ def save_task(task, broker: Broker):
|
|||||||
filters[Conf.SAVE_LIMIT_PER] = value
|
filters[Conf.SAVE_LIMIT_PER] = value
|
||||||
|
|
||||||
with db.transaction.atomic(using=db.router.db_for_write(Success)):
|
with db.transaction.atomic(using=db.router.db_for_write(Success)):
|
||||||
last = Success.objects.filter(**filters).select_for_update().last()
|
list(Success.objects.filter(**filters).select_for_update())
|
||||||
if (
|
if (
|
||||||
task["success"]
|
task["success"]
|
||||||
and 0 < Conf.SAVE_LIMIT <= Success.objects.filter(**filters).count()
|
and 0 < Conf.SAVE_LIMIT <= Success.objects.filter(**filters).count()
|
||||||
):
|
):
|
||||||
last.delete()
|
Success.objects.filter(**filters).last().delete()
|
||||||
|
|
||||||
# check if this task has previous results
|
# check if this task has previous results
|
||||||
try:
|
try:
|
||||||
existing_task = Task.objects.get(id=task["id"], name=task["name"])
|
task_obj = Task.objects.get(id=task["id"], name=task["name"])
|
||||||
# only update the result if it hasn't succeeded yet
|
# only update the result if it hasn't succeeded yet
|
||||||
if not existing_task.success:
|
if not task_obj.success:
|
||||||
existing_task.stopped = task["stopped"]
|
task_obj.stopped = task["stopped"]
|
||||||
existing_task.result = task["result"]
|
task_obj.result = task["result"]
|
||||||
existing_task.success = task["success"]
|
task_obj.success = task["success"]
|
||||||
existing_task.attempt_count = existing_task.attempt_count + 1
|
task_obj.attempt_count = task_obj.attempt_count + 1
|
||||||
existing_task.save()
|
task_obj.save()
|
||||||
|
|
||||||
if (
|
|
||||||
Conf.MAX_ATTEMPTS > 0
|
|
||||||
and existing_task.attempt_count >= Conf.MAX_ATTEMPTS
|
|
||||||
):
|
|
||||||
broker.acknowledge(task["ack_id"])
|
|
||||||
|
|
||||||
except Task.DoesNotExist:
|
except Task.DoesNotExist:
|
||||||
# convert func to string
|
# convert func to string
|
||||||
func = get_func_repr(task["func"])
|
func = get_func_repr(task["func"])
|
||||||
Task.objects.create(
|
task_obj = Task.objects.create(
|
||||||
id=task["id"],
|
id=task["id"],
|
||||||
name=task["name"],
|
name=task["name"],
|
||||||
func=func,
|
func=func,
|
||||||
@@ -150,6 +144,10 @@ def save_task(task, broker: Broker):
|
|||||||
success=task["success"],
|
success=task["success"],
|
||||||
attempt_count=1,
|
attempt_count=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if Conf.MAX_ATTEMPTS > 0 and task_obj.attempt_count >= Conf.MAX_ATTEMPTS:
|
||||||
|
broker.acknowledge(task["ack_id"])
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("Could not save task result")
|
logger.exception("Could not save task result")
|
||||||
|
|
||||||
|
|||||||
@@ -397,9 +397,7 @@ def memory(run_once=False, workers=False, broker=None):
|
|||||||
2,
|
2,
|
||||||
)
|
)
|
||||||
# memory available (MB)
|
# memory available (MB)
|
||||||
memory_available = round(
|
memory_available = round(psutil.virtual_memory().available / 1024**2, 2)
|
||||||
psutil.virtual_memory().available / 1024**2, 2
|
|
||||||
)
|
|
||||||
if memory_available_percentage < MEMORY_AVAILABLE_LOWEST_PERCENTAGE:
|
if memory_available_percentage < MEMORY_AVAILABLE_LOWEST_PERCENTAGE:
|
||||||
MEMORY_AVAILABLE_LOWEST_PERCENTAGE = memory_available_percentage
|
MEMORY_AVAILABLE_LOWEST_PERCENTAGE = memory_available_percentage
|
||||||
MEMORY_AVAILABLE_LOWEST_PERCENTAGE_AT = timezone.now()
|
MEMORY_AVAILABLE_LOWEST_PERCENTAGE_AT = timezone.now()
|
||||||
|
|||||||
+4
-4
@@ -4,8 +4,8 @@ from multiprocessing.queues import Queue
|
|||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
from django import core
|
from django import core
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
from django.apps.registry import apps
|
from django.apps.registry import apps
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
try:
|
try:
|
||||||
apps.check_apps_ready()
|
apps.check_apps_ready()
|
||||||
@@ -58,9 +58,9 @@ def pusher(task_queue: Queue, event: Event, broker: Broker = None):
|
|||||||
logger.exception("Failed to push task to queue")
|
logger.exception("Failed to push task to queue")
|
||||||
broker.fail(ack_id)
|
broker.fail(ack_id)
|
||||||
continue
|
continue
|
||||||
task[
|
task["cluster"] = (
|
||||||
"cluster"
|
Conf.CLUSTER_NAME
|
||||||
] = Conf.CLUSTER_NAME # save actual cluster name to orm task table
|
) # save actual cluster name to orm task table
|
||||||
task["ack_id"] = ack_id
|
task["ack_id"] = ack_id
|
||||||
task_queue.put(task)
|
task_queue.put(task)
|
||||||
logger.debug(
|
logger.debug(
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
The code is derived from
|
The code is derived from
|
||||||
https://github.com/althonos/pronto/commit/3384010dfb4fc7c66a219f59276adef3288a886b
|
https://github.com/althonos/pronto/commit/3384010dfb4fc7c66a219f59276adef3288a886b
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import multiprocessing.queues
|
import multiprocessing.queues
|
||||||
import sys
|
import sys
|
||||||
|
|||||||
@@ -2,11 +2,10 @@ import ast
|
|||||||
from multiprocessing.process import current_process
|
from multiprocessing.process import current_process
|
||||||
|
|
||||||
from django import core, db
|
from django import core, db
|
||||||
|
from django.apps.registry import apps
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from django.apps.registry import apps
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
apps.check_apps_ready()
|
apps.check_apps_ready()
|
||||||
except core.exceptions.AppRegistryNotReady:
|
except core.exceptions.AppRegistryNotReady:
|
||||||
@@ -86,7 +85,7 @@ def scheduler(broker: Broker = None):
|
|||||||
# Little Fix for already broken numbers
|
# Little Fix for already broken numbers
|
||||||
if s.repeats < -1:
|
if s.repeats < -1:
|
||||||
s.repeats = -1
|
s.repeats = -1
|
||||||
|
|
||||||
# Check if the value is not zero
|
# Check if the value is not zero
|
||||||
if s.repeats > 0:
|
if s.repeats > 0:
|
||||||
s.repeats -= 1
|
s.repeats -= 1
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
"""Package signing."""
|
"""Package signing."""
|
||||||
|
|
||||||
import pickle
|
import pickle
|
||||||
|
|
||||||
from django_q import core_signing as signing
|
from django_q import core_signing as signing
|
||||||
|
|||||||
+4
-5
@@ -1,4 +1,5 @@
|
|||||||
"""Provides task functionality."""
|
"""Provides task functionality."""
|
||||||
|
|
||||||
# Standard
|
# Standard
|
||||||
from multiprocessing import Value
|
from multiprocessing import Value
|
||||||
from time import sleep, time
|
from time import sleep, time
|
||||||
@@ -569,7 +570,9 @@ class Chain:
|
|||||||
A sequential chain of tasks
|
A sequential chain of tasks
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, chain=None, group=None, cached=Conf.CACHED, sync=Conf.SYNC, broker=None):
|
def __init__(
|
||||||
|
self, chain=None, group=None, cached=Conf.CACHED, sync=Conf.SYNC, broker=None
|
||||||
|
):
|
||||||
self.chain = chain or []
|
self.chain = chain or []
|
||||||
self.group = group or ""
|
self.group = group or ""
|
||||||
self.broker = broker or get_broker()
|
self.broker = broker or get_broker()
|
||||||
@@ -729,17 +732,14 @@ class AsyncTask:
|
|||||||
return self.id
|
return self.id
|
||||||
|
|
||||||
def result(self, wait=0):
|
def result(self, wait=0):
|
||||||
|
|
||||||
if self.started:
|
if self.started:
|
||||||
return result(self.id, wait=wait, cached=self.cached)
|
return result(self.id, wait=wait, cached=self.cached)
|
||||||
|
|
||||||
def fetch(self, wait=0):
|
def fetch(self, wait=0):
|
||||||
|
|
||||||
if self.started:
|
if self.started:
|
||||||
return fetch(self.id, wait=wait, cached=self.cached)
|
return fetch(self.id, wait=wait, cached=self.cached)
|
||||||
|
|
||||||
def result_group(self, failures=False, wait=0, count=None):
|
def result_group(self, failures=False, wait=0, count=None):
|
||||||
|
|
||||||
if self.started and self.group:
|
if self.started and self.group:
|
||||||
return result_group(
|
return result_group(
|
||||||
self.group,
|
self.group,
|
||||||
@@ -750,7 +750,6 @@ class AsyncTask:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def fetch_group(self, failures=True, wait=0, count=None):
|
def fetch_group(self, failures=True, wait=0, count=None):
|
||||||
|
|
||||||
if self.started and self.group:
|
if self.started and self.group:
|
||||||
return fetch_group(
|
return fetch_group(
|
||||||
self.group,
|
self.group,
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import signal
|
||||||
|
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from django_q.conf import logger
|
||||||
|
|
||||||
|
from .exceptions import TimeoutException
|
||||||
|
|
||||||
|
|
||||||
|
class TimeoutHandler:
|
||||||
|
def __init__(self, timeout: int):
|
||||||
|
self._timeout = timeout
|
||||||
|
|
||||||
|
def raise_timeout_exception(self, signum, frame):
|
||||||
|
raise TimeoutException(
|
||||||
|
f"Task exceeded maximum timeout value ({self._timeout} seconds)"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
# if the timeout is -1, then there is no timeout and the task will always keep running until it's done or manually killed
|
||||||
|
if self._timeout == -1:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
signal.signal(signal.SIGALRM, self.raise_timeout_exception)
|
||||||
|
except ValueError: # ValueError is raised for Windows users
|
||||||
|
logger.debug(_("SIGALARM is not available on your platform"))
|
||||||
|
|
||||||
|
signal.alarm(self._timeout)
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_value, traceback):
|
||||||
|
if self._timeout == -1:
|
||||||
|
return
|
||||||
|
"""When getting out of the timeout, reset the alarm, so it won't trigger"""
|
||||||
|
try:
|
||||||
|
signal.alarm(0)
|
||||||
|
signal.signal(signal.SIGALRM, signal.SIG_DFL)
|
||||||
|
except ValueError: # ValueError is raised for Windows users
|
||||||
|
logger.debug(_("SIGALARM is not available on your platform"))
|
||||||
+17
-3
@@ -5,9 +5,9 @@ from multiprocessing.process import current_process
|
|||||||
from multiprocessing.queues import Queue
|
from multiprocessing.queues import Queue
|
||||||
|
|
||||||
from django import core
|
from django import core
|
||||||
|
from django.apps.registry import apps
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.apps.registry import apps
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
apps.check_apps_ready()
|
apps.check_apps_ready()
|
||||||
@@ -17,7 +17,9 @@ except core.exceptions.AppRegistryNotReady:
|
|||||||
django.setup()
|
django.setup()
|
||||||
|
|
||||||
from django_q.conf import Conf, error_reporter, logger, resource, setproctitle
|
from django_q.conf import Conf, error_reporter, logger, resource, setproctitle
|
||||||
|
from django_q.exceptions import TimeoutException
|
||||||
from django_q.signals import post_spawn, pre_execute
|
from django_q.signals import post_spawn, pre_execute
|
||||||
|
from django_q.timeout import TimeoutHandler
|
||||||
from django_q.utils import close_old_django_connections, get_func_repr
|
from django_q.utils import close_old_django_connections, get_func_repr
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -89,25 +91,37 @@ def worker(
|
|||||||
pre_execute.send(sender="django_q", func=f, task=task)
|
pre_execute.send(sender="django_q", func=f, task=task)
|
||||||
# execute the payload
|
# execute the payload
|
||||||
timer.value = timer_value # Busy
|
timer.value = timer_value # Busy
|
||||||
|
if timer.value != -1:
|
||||||
|
timer.value += 3 # Add buffer so that guard doesn't kill the process on timeout before it gets processed
|
||||||
|
|
||||||
|
timeout_error = False
|
||||||
try:
|
try:
|
||||||
if f is None:
|
if f is None:
|
||||||
# raise a meaningfull error if task["func"] is not a valid function
|
# raise a meaningfull error if task["func"] is not a valid function
|
||||||
raise ValueError(f"Function {task['func']} is not defined")
|
raise ValueError(f"Function {task['func']} is not defined")
|
||||||
res = f(*task["args"], **task["kwargs"])
|
with TimeoutHandler(timer_value):
|
||||||
|
res = f(*task["args"], **task["kwargs"])
|
||||||
result = (res, True)
|
result = (res, True)
|
||||||
except Exception as e:
|
except (Exception, TimeoutException) as e:
|
||||||
|
if isinstance(e, TimeoutException):
|
||||||
|
timeout_error = True
|
||||||
result = (f"{e} : {traceback.format_exc()}", False)
|
result = (f"{e} : {traceback.format_exc()}", False)
|
||||||
if error_reporter:
|
if error_reporter:
|
||||||
error_reporter.report()
|
error_reporter.report()
|
||||||
if task.get("sync", False):
|
if task.get("sync", False):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
with timer.get_lock():
|
with timer.get_lock():
|
||||||
# Process result
|
# Process result
|
||||||
task["result"] = result[0]
|
task["result"] = result[0]
|
||||||
task["success"] = result[1]
|
task["success"] = result[1]
|
||||||
task["stopped"] = timezone.now()
|
task["stopped"] = timezone.now()
|
||||||
result_queue.put(task)
|
result_queue.put(task)
|
||||||
|
if timeout_error:
|
||||||
|
# force destroy process due to timeout
|
||||||
|
timer.value = 0
|
||||||
|
break
|
||||||
|
|
||||||
timer.value = -1 # Idle
|
timer.value = -1 # Idle
|
||||||
if setproctitle:
|
if setproctitle:
|
||||||
setproctitle.setproctitle(f"qcluster {proc_name} idle")
|
setproctitle.setproctitle(f"qcluster {proc_name} idle")
|
||||||
|
|||||||
+2
-2
@@ -73,9 +73,9 @@ author = "Ilan Steemers, Stan Triepels"
|
|||||||
# built documents.
|
# built documents.
|
||||||
#
|
#
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
version = "1.6"
|
version = "1.7"
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = "1.6.2"
|
release = "1.7.2"
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
# for a list of supported languages.
|
# for a list of supported languages.
|
||||||
|
|||||||
@@ -79,6 +79,8 @@ timeout
|
|||||||
The number of seconds a worker is allowed to spend on a task before it's terminated. Defaults to ``None``, meaning it will never time out.
|
The number of seconds a worker is allowed to spend on a task before it's terminated. Defaults to ``None``, meaning it will never time out.
|
||||||
Set this to something that makes sense for your project. Can be overridden for individual tasks.
|
Set this to something that makes sense for your project. Can be overridden for individual tasks.
|
||||||
|
|
||||||
|
Note: for systems that don't have `SIGALRM` available (e.g. Windows), it will not raise an error properly. It will kill the task, but it will keep retrying until it finishes within the given time.
|
||||||
|
|
||||||
See :ref:`retry` for details how to set values for timeout and retry.
|
See :ref:`retry` for details how to set values for timeout and retry.
|
||||||
|
|
||||||
.. _time_zone:
|
.. _time_zone:
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
version: "3.0"
|
|
||||||
services:
|
services:
|
||||||
docs:
|
docs:
|
||||||
container_name: djangoq2-docs
|
container_name: djangoq2-docs
|
||||||
|
|||||||
+1
-1
@@ -27,7 +27,7 @@ Features
|
|||||||
- Rollbar and Sentry support
|
- Rollbar and Sentry support
|
||||||
|
|
||||||
|
|
||||||
Django Q2 is tested with: Python 3.8, 3.9, 3.10, 3.11 and 3.12. Works with Django 3.2.x, 4.1.x, 4.2.x and 5.0.x
|
Django Q2 is tested with: Python 3.8, 3.9, 3.10, 3.11 and 3.12. Works with Django 4.2.x and 5.0.x
|
||||||
|
|
||||||
Currently available in English, German and French.
|
Currently available in English, German and French.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
ASGI config for exampleproject project.
|
||||||
|
|
||||||
|
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "exampleproject.settings")
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
"""
|
||||||
|
Django settings for exampleproject project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 5.1.1.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.1/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/5.1/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = "django-insecure-)oouh93bjg+c=b!l5-*w)7et1l+!3nmp223rr^1r4v#jn&ow3f"
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = ["*"]
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
"django.contrib.admin",
|
||||||
|
"django.contrib.auth",
|
||||||
|
"django.contrib.contenttypes",
|
||||||
|
"django.contrib.sessions",
|
||||||
|
"django.contrib.messages",
|
||||||
|
"django.contrib.staticfiles",
|
||||||
|
"django_q",
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
"django.middleware.security.SecurityMiddleware",
|
||||||
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
|
"django.middleware.common.CommonMiddleware",
|
||||||
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
|
"django.contrib.messages.middleware.MessageMiddleware",
|
||||||
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = "exampleproject.urls"
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
|
"DIRS": [],
|
||||||
|
"APP_DIRS": True,
|
||||||
|
"OPTIONS": {
|
||||||
|
"context_processors": [
|
||||||
|
"django.template.context_processors.debug",
|
||||||
|
"django.template.context_processors.request",
|
||||||
|
"django.contrib.auth.context_processors.auth",
|
||||||
|
"django.contrib.messages.context_processors.messages",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = "exampleproject.wsgi.application"
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/5.1/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
"default": {
|
||||||
|
"ENGINE": "django.db.backends.sqlite3",
|
||||||
|
"NAME": BASE_DIR / "db.sqlite3",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{
|
||||||
|
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/5.1/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = "en-us"
|
||||||
|
|
||||||
|
TIME_ZONE = "UTC"
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/5.1/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = "static/"
|
||||||
|
|
||||||
|
# Default primary key field type
|
||||||
|
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||||
|
|
||||||
|
Q_CLUSTER = {
|
||||||
|
"name": "DjangORM",
|
||||||
|
"workers": 4,
|
||||||
|
"timeout": 90,
|
||||||
|
"retry": 120,
|
||||||
|
"queue_limit": 50,
|
||||||
|
"bulk": 10,
|
||||||
|
"orm": "default",
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
"""
|
||||||
|
URL configuration for exampleproject project.
|
||||||
|
|
||||||
|
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||||
|
https://docs.djangoproject.com/en/5.1/topics/http/urls/
|
||||||
|
Examples:
|
||||||
|
Function views
|
||||||
|
1. Add an import: from my_app import views
|
||||||
|
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||||
|
Class-based views
|
||||||
|
1. Add an import: from other_app.views import Home
|
||||||
|
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||||
|
Including another URLconf
|
||||||
|
1. Import the include() function: from django.urls import include, path
|
||||||
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from exampleproject import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("admin/", admin.site.urls),
|
||||||
|
path("new_task/", views.add_task, name="add_task"),
|
||||||
|
path("result/<slug:task_id>/", views.get_result, name="get_result"),
|
||||||
|
]
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import time
|
||||||
|
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from django_q import tasks
|
||||||
|
|
||||||
|
|
||||||
|
# internal function to be called with django_q
|
||||||
|
def new_task(run_for_minutes):
|
||||||
|
print("Task started")
|
||||||
|
time.sleep(run_for_minutes)
|
||||||
|
print("Task done")
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def add_task(request):
|
||||||
|
task_id = tasks.async_task(new_task, 5)
|
||||||
|
result_url = reverse("get_result", args=[task_id])
|
||||||
|
return HttpResponse(
|
||||||
|
f"Added async task with <a href='{result_url}'>Go to results</a>"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_result(request, task_id):
|
||||||
|
task = tasks.fetch(task_id)
|
||||||
|
if not task:
|
||||||
|
msg = "Task running... please refresh after some time"
|
||||||
|
else:
|
||||||
|
msg = f"Async task result: {task.result}"
|
||||||
|
return HttpResponse(msg)
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
WSGI config for exampleproject project.
|
||||||
|
|
||||||
|
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "exampleproject.settings")
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
||||||
Generated
+1274
-1487
File diff suppressed because it is too large
Load Diff
+3
-4
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
|
|||||||
|
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "django-q2"
|
name = "django-q2"
|
||||||
version = "1.6.2"
|
version = "1.7.2"
|
||||||
packages = [
|
packages = [
|
||||||
{ include = "django_q" },
|
{ include = "django_q" },
|
||||||
]
|
]
|
||||||
@@ -50,7 +50,7 @@ include = ['CHANGELOG.md']
|
|||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = ">=3.8,<4"
|
python = ">=3.8,<4"
|
||||||
django = { version = ">=3.2, <6", allow-prereleases = true}
|
django = ">=4.2, <6"
|
||||||
django-picklefield = "^3.1"
|
django-picklefield = "^3.1"
|
||||||
|
|
||||||
blessed = { version = "^1.19.1", optional = true }
|
blessed = { version = "^1.19.1", optional = true }
|
||||||
@@ -73,8 +73,7 @@ pytest = "^7.1.3"
|
|||||||
pytest-django = "^4.5.2"
|
pytest-django = "^4.5.2"
|
||||||
Sphinx = "^4.0.2"
|
Sphinx = "^4.0.2"
|
||||||
pytest-cov = "^4.0.0"
|
pytest-cov = "^4.0.0"
|
||||||
black = "^22.10.0"
|
ruff = "^0.4.4"
|
||||||
isort = {extras = ["requirements_deprecated_finder"], version = "^5.10.1"}
|
|
||||||
|
|
||||||
[tool.poetry.extras]
|
[tool.poetry.extras]
|
||||||
requires = ["poetry_core"]
|
requires = ["poetry_core"]
|
||||||
|
|||||||
+41
-40
@@ -1,40 +1,41 @@
|
|||||||
ansicon==1.89.0; platform_system == "Windows" and python_version >= "2.7" \
|
asgiref==3.8.1 ; python_version >= "3.8" and python_version < "4" \
|
||||||
--hash=sha256:f1def52d17f65c2c9682cf8370c03f541f410c1752d6a14029f97318e4b9dfec \
|
--hash=sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47 \
|
||||||
--hash=sha256:e4d039def5768a47e4afec8e89e83ec3ae5a26bf00ad851f914d1240b444d2b1
|
--hash=sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590
|
||||||
arrow==1.1.1; python_version >= "3.6" \
|
backports-zoneinfo==0.2.1 ; python_version >= "3.8" and python_version < "3.9" \
|
||||||
--hash=sha256:77a60a4db5766d900a2085ce9074c5c7b8e2c99afeaa98ad627637ff6f292510 \
|
--hash=sha256:17746bd546106fa389c51dbea67c8b7c8f0d14b5526a579ca6ccf5ed72c526cf \
|
||||||
--hash=sha256:dee7602f6c60e3ec510095b5e301441bc56288cb8f51def14dcb3079f623823a
|
--hash=sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328 \
|
||||||
asgiref==3.3.4; python_version >= "3.6" \
|
--hash=sha256:1c5742112073a563c81f786e77514969acb58649bcdf6cdf0b4ed31a348d4546 \
|
||||||
--hash=sha256:92906c611ce6c967347bbfea733f13d6313901d54dcca88195eaeb52b2a8e8ee \
|
--hash=sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6 \
|
||||||
--hash=sha256:d1216dfbdfb63826470995d31caed36225dcaf34f182e0fa257a4dd9e86f1b78
|
--hash=sha256:5c144945a7752ca544b4b78c8c41544cdfaf9786f25fe5ffb10e838e19a27570 \
|
||||||
blessed==1.18.1; python_version >= "2.7" \
|
--hash=sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9 \
|
||||||
--hash=sha256:dd7c0d33db9a2e7f597b446996484d0ed46e1586239db064fb5025008937dcae \
|
--hash=sha256:8439c030a11780786a2002261569bdf362264f605dfa4d65090b64b05c9f79a7 \
|
||||||
--hash=sha256:8b09936def6bc06583db99b65636b980075733e13550cb6af262ce724a55da23
|
--hash=sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987 \
|
||||||
django-picklefield==3.0.1; python_version >= "3" \
|
--hash=sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722 \
|
||||||
--hash=sha256:15ccba592ca953b9edf9532e64640329cd47b136b7f8f10f2939caa5f9ce4287 \
|
--hash=sha256:a76b38c52400b762e48131494ba26be363491ac4f9a04c1b7e92483d169f6582 \
|
||||||
--hash=sha256:3c702a54fde2d322fe5b2f39b8f78d9f655b8f77944ab26f703be6c0ed335a35
|
--hash=sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc \
|
||||||
django==3.2.4; python_version >= "3.6" \
|
--hash=sha256:e55b384612d93be96506932a786bbcde5a2db7a9e6a4bb4bffe8b733f5b9036b \
|
||||||
--hash=sha256:ea735cbbbb3b2fba6d4da4784a0043d84c67c92f1fdf15ad6db69900e792c10f \
|
--hash=sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1 \
|
||||||
--hash=sha256:66c9d8db8cc6fe938a28b7887c1596e42d522e27618562517cc8929eb7e7f296
|
--hash=sha256:e8236383a20872c0cdf5a62b554b27538db7fa1bbec52429d8d106effbaeca08 \
|
||||||
jinxed==1.1.0; platform_system == "Windows" and python_version >= "2.7" \
|
--hash=sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac \
|
||||||
--hash=sha256:6a61ccf963c16aa885304f27e6e5693783676897cea0c7f223270c8b8e78baf8 \
|
--hash=sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2
|
||||||
--hash=sha256:d8f1731f134e9e6b04d95095845ae6c10eb15cb223a5f0cabdea87d4a279c305
|
django-picklefield==3.2 ; python_version >= "3.8" and python_version < "4" \
|
||||||
python-dateutil==2.8.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6" \
|
--hash=sha256:aa463f5d79d497dbe789f14b45180f00a51d0d670067d0729f352a3941cdfa4d \
|
||||||
--hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \
|
--hash=sha256:e9a73539d110f69825d9320db18bcb82e5189ff48dbed41821c026a20497764c
|
||||||
--hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a
|
django==4.2.11 ; python_version >= "3.8" and python_version < "4" \
|
||||||
pytz==2021.1; python_version >= "3.6" \
|
--hash=sha256:6e6ff3db2d8dd0c986b4eec8554c8e4f919b5c1ff62a5b4390c17aff2ed6e5c4 \
|
||||||
--hash=sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798 \
|
--hash=sha256:ddc24a0a8280a0430baa37aff11f28574720af05888c62b7cfe71d219f4599d3
|
||||||
--hash=sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da
|
importlib-metadata==7.1.0 ; python_version >= "3.8" and python_version < "3.10" \
|
||||||
six==1.16.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6" \
|
--hash=sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570 \
|
||||||
--hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 \
|
--hash=sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2
|
||||||
--hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926
|
sqlparse==0.5.0 ; python_version >= "3.8" and python_version < "4" \
|
||||||
sqlparse==0.4.1; python_version >= "3.6" \
|
--hash=sha256:714d0a4932c059d16189f58ef5411ec2287a4360f17cdd0edd2d09d4c5087c93 \
|
||||||
--hash=sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0 \
|
--hash=sha256:c204494cd97479d0e39f28c93d46c0b2d5959c7b9ab904762ea6c7af211c8663
|
||||||
--hash=sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8
|
typing-extensions==4.11.0 ; python_version >= "3.8" and python_version < "3.11" \
|
||||||
typing-extensions==3.10.0.0; python_version < "3.8" and python_version >= "3.6" \
|
--hash=sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0 \
|
||||||
--hash=sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497 \
|
--hash=sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a
|
||||||
--hash=sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84 \
|
tzdata==2024.1 ; python_version >= "3.8" and python_version < "4" and sys_platform == "win32" \
|
||||||
--hash=sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342
|
--hash=sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd \
|
||||||
wcwidth==0.2.5; python_version >= "2.7" \
|
--hash=sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252
|
||||||
--hash=sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784 \
|
zipp==3.18.1 ; python_version >= "3.8" and python_version < "3.10" \
|
||||||
--hash=sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83
|
--hash=sha256:206f5a15f2af3dbaee80769fb7dc6f249695e940acca08dfb2a4769fe61e538b \
|
||||||
|
--hash=sha256:2884ed22e7d8961de1c9a05142eb69a247f120291bc0206a00a7642f09b5b715
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
redis:
|
redis:
|
||||||
image: redis:latest
|
image: redis:latest
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
web:
|
web:
|
||||||
restart: always
|
restart: always
|
||||||
command: python manage.py runserver 0.0.0.0:8000
|
command: bash -c "python manage.py migrate && python manage.py runserver 0.0.0.0:8000"
|
||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:8000:8000"
|
- "127.0.0.1:8000:8000"
|
||||||
build: .
|
build: .
|
||||||
volumes:
|
volumes:
|
||||||
- .:/app
|
- .:/app
|
||||||
|
|
||||||
|
django-q:
|
||||||
|
restart: always
|
||||||
|
command: bash -c "python manage.py migrate && python manage.py qcluster"
|
||||||
|
build: .
|
||||||
|
volumes:
|
||||||
|
- .:/app
|
||||||
|
|||||||
Reference in New Issue
Block a user