Remove arrow dependency, update packages, set up testing with compose (#17)

This commit is contained in:
Stan Triepels
2022-10-19 02:01:16 +02:00
committed by GitHub
parent 5539dd633b
commit c59f036158
20 changed files with 705 additions and 822 deletions
+6 -2
View File
@@ -11,8 +11,9 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [ "3.7", "3.8", "3.9", "3.10" ]
django: [ "2.2", "3.2" ]
python-version: [ "3.8", "3.9", "3.10" ]
django: [ "3.2", "4.1" ]
services:
disque:
image: efrecon/disque:1.0-rc1
@@ -52,6 +53,9 @@ jobs:
- name: Run Tests
run: |
poetry run pytest --cov=./django_q --cov-report=xml
env:
MONGO_HOST: "127.0.0.1"
REDIS_HOST: "127.0.0.1"
- name: Upload to coveralls
run: |
python -m pip install coveralls
+8 -24
View File
@@ -7,34 +7,18 @@ ENV PYTHONUNBUFFERED 1
# Sets the default shell to bash
ENV SHELL /bin/bash
# Creates a non-root user
RUN adduser --disabled-password docker
# Upgrades pip
RUN pip install --upgrade pip
RUN pip install --upgrade pip
# Poetry project setup for development
# Copies poetry requirements files
COPY --chown=docker Dockerfile.dev requirements.txt* setup.py* ./
# Install poetry
RUN pip install poetry
RUN pip install -r requirements.txt
WORKDIR /app
RUN pip install pytest pytest-django codecov poetry
COPY . .
# Clean up
RUN apt-get autoremove -y \
&& apt-get clean -y \
&& rm -rf /var/lib/apt/lists/*
RUN poetry lock --no-update
WORKDIR /home/docker
RUN poetry config virtualenvs.create false
# Sets the binaries to path
ENV PATH="/home/docker/.local/bin:${PATH}"
# Copy in as non-root user, so permissions match what we need
COPY --chown=docker:docker . .
RUN python setup.py develop
# Applies the container user to be non-root
USER docker
RUN poetry install -E testing
+8 -40
View File
@@ -19,23 +19,21 @@ Features
- Django Admin integration
- PaaS compatible with multiple instances
- Multi cluster monitor
- Redis, Disque, IronMQ, SQS, MongoDB or ORM
- Redis, IronMQ, SQS, MongoDB or ORM
- Rollbar and Sentry support
Requirements
~~~~~~~~~~~~
- `Django <https://www.djangoproject.com>`__ > = 2.2
- `Django <https://www.djangoproject.com>`__ > = 3.2
- `Django-picklefield <https://github.com/gintas/django-picklefield>`__
- `Arrow <https://github.com/crsmithdev/arrow>`__
- `Blessed <https://github.com/jquast/blessed>`__
Tested with: Python 3.7, 3.8, 3.9, 3.10 Django 2.2.X and 3.2.X
Tested with: Python 3.7, 3.8, 3.9, 3.10 Django 3.2.X and 4.1.X
Brokers
~~~~~~~
- `Redis <https://django-q2.readthedocs.org/en/latest/brokers.html#redis>`__
- `Disque <https://django-q2.readthedocs.org/en/latest/brokers.html#disque>`__
- `IronMQ <https://django-q2.readthedocs.org/en/latest/brokers.html#ironmq>`__
- `Amazon SQS <https://django-q2.readthedocs.org/en/latest/brokers.html#amazon-sqs>`__
- `MongoDB <https://django-q2.readthedocs.org/en/latest/brokers.html#mongodb>`__
@@ -173,14 +171,14 @@ Admin page or directly from your code:
# Run a task every 5 minutes, starting at 6 today
# for 2 hours
import arrow
from datetime import datetime
schedule('math.hypot',
3, 4,
schedule_type=Schedule.MINUTES,
minutes=5,
repeats=24,
next_run=arrow.utcnow().replace(hour=18, minute=0))
next_run=datetime.utcnow().replace(hour=18, minute=0))
# Use a cron expression
schedule('math.hypot',
@@ -194,45 +192,15 @@ For more info check the `Schedules <https://django-q2.readthedocs.org/en/latest/
Testing
~~~~~~~
To run the tests you will need the following in addition to install requirements:
* `py.test <http://pytest.org/latest/>`__
* `pytest-django <https://github.com/pytest-dev/pytest-django>`__
* Disque from https://github.com/antirez/disque.git
* Redis
* MongoDB
Or you can use the included Docker Compose file.
The following commands can be used to run the tests:
Running tests is easy with docker compose, it will also start the necessary databases. Just run:
.. code:: bash
# Create virtual environment
python -m venv venv
# Install requirements
venv/bin/pip install -r requirements.txt
# Install test dependencies
venv/bin/pip install pytest pytest-django
# Install django-q
venv/bin/python setup.py develop
# Run required services (you need to have docker-compose installed)
docker-compose -f test-services-docker-compose.yaml up -d
# Run tests
venv/bin/pytest
# Stop the services required by tests (when you no longer plan to run tests)
docker-compose -f test-services-docker-compose.yaml down
docker-compose -f test-services-docker-compose.yaml run --rm django-q2 poetry run pytest
Locale
~~~~~~
Currently available in English, German and French.
Currently available in English, German, Turkish, and French.
Translation pull requests are always welcome.
Todo
-5
View File
@@ -173,11 +173,6 @@ def get_broker(list_key: str = Conf.PREFIX) -> Broker:
m = importlib.import_module(module)
broker = getattr(m, func)
return broker(list_key=list_key)
# disque
elif Conf.DISQUE_NODES:
from django_q.brokers import disque
return disque.Disque(list_key=list_key)
# Iron MQ
elif Conf.IRON_MQ:
from django_q.brokers import ironmq
-78
View File
@@ -1,78 +0,0 @@
import random
# External
import redis
# Django
from django.utils.translation import gettext_lazy as _
from redis import Redis
from django_q.brokers import Broker
from django_q.conf import Conf
class Disque(Broker):
def enqueue(self, task):
retry = Conf.RETRY if Conf.RETRY > 0 else f"{Conf.RETRY} REPLICATE 1"
return self.connection.execute_command(
f"ADDJOB {self.list_key} {task} 500 RETRY {retry}"
).decode()
def dequeue(self):
tasks = self.connection.execute_command(
f"GETJOB COUNT {Conf.BULK} TIMEOUT 1000 FROM {self.list_key}"
)
if tasks:
return [(t[1].decode(), t[2].decode()) for t in tasks]
def queue_size(self):
return self.connection.execute_command(f"QLEN {self.list_key}")
def acknowledge(self, task_id):
command = "FASTACK" if Conf.DISQUE_FASTACK else "ACKJOB"
return self.connection.execute_command(f"{command} {task_id}")
def ping(self) -> bool:
return self.connection.execute_command("HELLO")[0] > 0
def delete(self, task_id):
return self.connection.execute_command(f"DELJOB {task_id}")
def fail(self, task_id):
return self.delete(task_id)
def delete_queue(self) -> int:
jobs = self.connection.execute_command(f"JSCAN QUEUE {self.list_key}")[1]
if jobs:
job_ids = " ".join(jid.decode() for jid in jobs)
self.connection.execute_command(f"DELJOB {job_ids}")
return len(jobs)
def info(self) -> str:
if not self._info:
info = self.connection.info("server")
self._info = f'Disque {info["disque_version"]}'
return self._info
@staticmethod
def get_connection(list_key: str = Conf.PREFIX) -> Redis:
if not Conf.DISQUE_NODES:
raise redis.exceptions.ConnectionError(_("No Disque nodes configured"))
# randomize nodes
random.shuffle(Conf.DISQUE_NODES)
# find one that works
for node in Conf.DISQUE_NODES:
host, port = node.split(":")
kwargs = {"host": host, "port": port}
if Conf.DISQUE_AUTH:
kwargs["password"] = Conf.DISQUE_AUTH
redis_client = redis.Redis(**kwargs)
redis_client.decode_responses = True
try:
redis_client.execute_command("HELLO")
return redis_client
except redis.exceptions.ConnectionError:
continue
raise redis.exceptions.ConnectionError(
_("Could not connect to any Disque nodes")
)
+15 -23
View File
@@ -6,13 +6,10 @@ import signal
import socket
import traceback
import uuid
from datetime import datetime
from datetime import datetime, timedelta
from multiprocessing import Event, Process, Value, current_process
from time import sleep
# External
import arrow
# Django
from django import core, db
from django.apps.registry import apps
@@ -47,6 +44,8 @@ from django_q.signals import post_execute, pre_execute
from django_q.signing import BadSignature, SignedPackage
from django_q.status import Stat, Status
from .utils import add_months, add_years
class Cluster:
def __init__(self, broker: Broker = None):
@@ -635,22 +634,22 @@ def scheduler(broker: Broker = None):
q_options["hook"] = s.hook
# set up the next run time
if s.schedule_type != s.ONCE:
next_run = arrow.get(s.next_run)
next_run = s.next_run
while True:
if s.schedule_type == s.MINUTES:
next_run = next_run.shift(minutes=+(s.minutes or 1))
next_run = next_run + timedelta(minutes=(s.minutes or 1))
elif s.schedule_type == s.HOURLY:
next_run = next_run.shift(hours=+1)
next_run = next_run + timedelta(hours=1)
elif s.schedule_type == s.DAILY:
next_run = next_run.shift(days=+1)
next_run = next_run + timedelta(days=1)
elif s.schedule_type == s.WEEKLY:
next_run = next_run.shift(weeks=+1)
next_run = next_run + timedelta(weeks=1)
elif s.schedule_type == s.MONTHLY:
next_run = next_run.shift(months=+1)
next_run = add_months(next_run, 1)
elif s.schedule_type == s.QUARTERLY:
next_run = next_run.shift(months=+3)
next_run = add_months(next_run, 3)
elif s.schedule_type == s.YEARLY:
next_run = next_run.shift(years=+1)
next_run = add_years(next_run, 1)
elif s.schedule_type == s.CRON:
if not croniter:
raise ImportError(
@@ -658,18 +657,11 @@ def scheduler(broker: Broker = None):
"Please install croniter to enable cron expressions"
)
)
next_run = arrow.get(
croniter(s.cron, localtime()).get_next()
)
if Conf.CATCH_UP or next_run > arrow.utcnow():
next_run = croniter(s.cron, localtime()).get_next(datetime)
if Conf.CATCH_UP or next_run > localtime():
break
# arrow always returns a tz aware datetime, and we don't want
# this when we explicitly configured django with USE_TZ=False
s.next_run = (
next_run.datetime
if settings.USE_TZ
else next_run.datetime.replace(tzinfo=None)
)
s.next_run = next_run
s.repeats += -1
# send it to the cluster
scheduled_broker = broker
-9
View File
@@ -45,15 +45,6 @@ class Conf:
DJANGO_REDIS = conf.get("django_redis", None)
# Disque broker
DISQUE_NODES = conf.get("disque_nodes", None)
# Optional Authentication
DISQUE_AUTH = conf.get("disque_auth", None)
# Optional Fast acknowledge
DISQUE_FASTACK = conf.get("disque_fastack", False)
# IronMQ broker
IRON_MQ = conf.get("iron_mq", None)
+7 -1
View File
@@ -106,11 +106,16 @@ LOGGING = {
STATIC_URL = "/static/"
REDIS_HOST = os.environ.get("REDIS_HOST", "redis")
MONGO_HOST = os.environ.get("MONGO_HOST", "mongo")
# Django Redis
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/0",
"LOCATION": f"redis://{REDIS_HOST}:6379/0",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"PARSER_CLASS": "redis.connection.HiredisParser",
@@ -125,4 +130,5 @@ Q_CLUSTER = {
"testing": True,
"log_level": "DEBUG",
"django_redis": "default",
"redis": f"redis://{REDIS_HOST}:6379/0"
}
+4 -65
View File
@@ -7,6 +7,7 @@ import redis
from django_q.brokers import Broker, get_broker
from django_q.conf import Conf
from django_q.humanhash import uuid
from django_q.tests.settings import REDIS_HOST, MONGO_HOST
def test_broker(monkeypatch):
@@ -40,11 +41,11 @@ def test_redis(monkeypatch):
broker = get_broker()
assert broker.ping() is True
assert broker.info() is not None
monkeypatch.setattr(Conf, "REDIS", {"host": "127.0.0.1", "port": 7799})
monkeypatch.setattr(Conf, "REDIS", {"host": REDIS_HOST, "port": 7799})
broker = get_broker()
with pytest.raises(Exception):
broker.ping()
monkeypatch.setattr(Conf, "REDIS", "redis://127.0.0.1:7799")
monkeypatch.setattr(Conf, "REDIS", f"redis://{REDIS_HOST}:7799")
broker = get_broker()
with pytest.raises(Exception):
broker.ping()
@@ -58,68 +59,6 @@ def test_custom(monkeypatch):
assert broker.__class__.__name__ == "Redis"
def test_disque(monkeypatch):
monkeypatch.setattr(Conf, "DISQUE_NODES", ["127.0.0.1:7711"])
# check broker
broker = get_broker(list_key="disque_test")
assert broker.ping() is True
assert broker.info() is not None
# clear before we start
broker.delete_queue()
# async_task
broker.enqueue("test")
assert broker.queue_size() == 1
# dequeue
task = broker.dequeue()[0]
assert task[1] == "test"
broker.acknowledge(task[0])
assert broker.queue_size() == 0
# Retry test
monkeypatch.setattr(Conf, "RETRY", 1)
broker.enqueue("test")
assert broker.queue_size() == 1
broker.dequeue()
assert broker.queue_size() == 0
sleep(1.5)
assert broker.queue_size() == 1
task = broker.dequeue()[0]
assert broker.queue_size() == 0
broker.acknowledge(task[0])
sleep(1.5)
assert broker.queue_size() == 0
# delete job
task_id = broker.enqueue("test")
broker.delete(task_id)
assert broker.dequeue() is None
# fail
task_id = broker.enqueue("test")
broker.fail(task_id)
# bulk test
for _ in range(5):
broker.enqueue("test")
monkeypatch.setattr(Conf, "BULK", 5)
monkeypatch.setattr(Conf, "DISQUE_FASTACK", True)
tasks = broker.dequeue()
for task in tasks:
assert task is not None
broker.acknowledge(task[0])
# test duplicate acknowledge
broker.acknowledge(task[0])
# delete queue
broker.enqueue("test")
broker.enqueue("test")
broker.delete_queue()
assert broker.queue_size() == 0
# connection test
monkeypatch.setattr(Conf, "DISQUE_NODES", ["127.0.0.1:7798", "127.0.0.1:7799"])
with pytest.raises(redis.exceptions.ConnectionError):
broker.get_connection()
# connection test with no nodes
monkeypatch.setattr(Conf, "DISQUE_NODES", None)
with pytest.raises(redis.exceptions.ConnectionError):
broker.get_connection()
@pytest.mark.skipif(
not os.getenv("IRON_MQ_TOKEN"), reason="requires IronMQ credentials"
)
@@ -312,7 +251,7 @@ def test_orm(monkeypatch):
@pytest.mark.django_db
def test_mongo(monkeypatch):
monkeypatch.setattr(Conf, "MONGO", {"host": "127.0.0.1", "port": 27017})
monkeypatch.setattr(Conf, "MONGO", {"host": MONGO_HOST, "port": 27017})
# check broker
broker = get_broker(list_key="mongo_test")
assert broker.ping() is True
+42
View File
@@ -1,3 +1,4 @@
from datetime import datetime
import os
import sys
import threading
@@ -32,6 +33,7 @@ from django_q.tasks import (
result_group,
)
from django_q.tests.tasks import TaskError, multiply
from django_q.utils import add_months, add_years
class WordClass:
@@ -743,3 +745,43 @@ def assert_result(task):
def assert_bad_result(task):
assert task is not None
assert task.success is False
@pytest.mark.django_db
def test_add_months():
# add some months
initial_date = datetime(2020, 2, 2)
new_date = add_months(initial_date, 3)
assert new_date.year == 2020
assert new_date.month == 5
assert new_date.day == 2
# push to next year
initial_date = datetime(2020, 11, 2)
new_date = add_months(initial_date, 3)
assert new_date.year == 2021
assert new_date.month == 2
assert new_date.day == 2
# last day of the month
initial_date = datetime(2020, 1, 31)
new_date = add_months(initial_date, 1)
assert new_date.year == 2020
assert new_date.month == 2
assert new_date.day == 29
@pytest.mark.django_db
def test_add_years():
# add some months
initial_date = datetime(2020, 2, 2)
new_date = add_years(initial_date, 1)
assert new_date.year == 2021
assert new_date.month == 2
assert new_date.day == 2
# test leap year
initial_date = datetime(2020, 2, 29)
new_date = add_years(initial_date, 1)
assert new_date.year == 2021
assert new_date.month == 2
assert new_date.day == 28
+1 -2
View File
@@ -3,7 +3,6 @@ from datetime import timedelta
from multiprocessing import Event, Value
from unittest import mock
import arrow
import pytest
from django.core.exceptions import ValidationError
from django.db import IntegrityError
@@ -128,7 +127,7 @@ def test_scheduler(broker, monkeypatch):
assert schedule.repeats == 0
assert schedule.last_run() is not None
assert schedule.success() is True
assert schedule.next_run < arrow.get(timezone.now()).shift(hours=+1)
assert schedule.next_run < timezone.now() + timedelta(hours=1)
task = fetch(schedule.task)
assert task is not None
assert task.success is True
+30
View File
@@ -0,0 +1,30 @@
import datetime
from datetime import date
from django.utils.timezone import make_aware
import calendar
# credits: https://stackoverflow.com/a/4131114
# Made them aware of timezone
def add_months(d, months):
month = d.month - 1 + months
year = d.year + month // 12
month = month % 12 + 1
day = min(d.day, calendar.monthrange(year,month)[1])
return d.replace(year=year, month=month, day=day)
# credits: https://stackoverflow.com/a/15743908
# Changed the last line to make it a little easier to read and changed it to move February 29 to 28 next year
# Also made them aware of timezone
def add_years(d, years):
"""Return a date that's `years` years after the date (or datetime)
object `d`. Return the same calendar date (month and day) in the
destination year, if it exists, otherwise use the previous day
(thus changing February 29 to February 28).
"""
try:
return d.replace(year = d.year + years)
except ValueError:
new_date = d + (date(d.year + years, 3, 1) - date(d.year, 3, 1))
return d.replace(year=new_date.year, month=new_date.month, day=new_date.day)
+1 -17
View File
@@ -2,7 +2,7 @@ Brokers
=======
The broker sits between your Django instances and your Django Q2 cluster instances; accepting, saving and delivering task packages.
Currently we support a variety of brokers from the default Redis, bleeding edge Disque to the convenient ORM and fast MongoDB.
Currently we support a variety of brokers.
The default Redis broker does not support message receipts.
This means that in case of a catastrophic failure of the cluster server or worker timeouts, tasks that were being executed get lost.
@@ -38,22 +38,6 @@ The default broker for Django Q2 clusters.
* Can use existing :ref:`django_redis` connections.
* Configure with :ref:`redis_configuration`-py compatible configuration
Disque
------
Unlike Redis, Disque supports message receipts which make delivery to the cluster workers guaranteed.
In our tests it is as fast or faster than the Redis broker.
You can control the amount of time Disque should wait for completion of a task by configuring the :ref:`retry` setting.
Bulk task retrieval is supported via the :ref:`bulk` option.
* Delivery receipts
* Atomic
* Needs Django's `Cache framework <https://docs.djangoproject.com/en/4.0/topics/cache/#setting-up-the-cache>`__ configured for monitoring
* Compatible with `Tynd <https://disque.tynd.co/>`__ Disque addon on `Heroku <https://heroku.com>`__
* Still considered Alpha software
* Supports bulk dequeue
* Requires `Redis-py <https://github.com/andymccurdy/redis-py>`__ client library: ``pip install redis``
* See the :ref:`disque_configuration` configuration section for more info.
IronMQ
------
This HTTP based queue service is both available directly via `Iron.io <http://www.iron.io/mq/>`__ and as an add-on on Heroku.
-37
View File
@@ -235,43 +235,6 @@ of the cache connection you want to use instead of a direct Redis connection::
.. tip::
Django Q2 uses your ``SECRET_KEY`` to sign task packages and prevent task crossover. So make sure you have it set up in your Django settings.
.. _disque_configuration:
disque_nodes
~~~~~~~~~~~~
If you want to use Disque as your broker, set this to a list of available Disque nodes and each cluster will randomly try to connect to them::
# example disque connection
Q_CLUSTER = {
'name': 'DisqueBroker',
'workers': 4,
'timeout': 60,
'retry': 60,
'disque_nodes': ['127.0.0.1:7711', '127.0.0.1:7712']
}
Django Q2 is also compatible with the `Tynd Disque <https://disque.tynd.co/>`__ addon on `Heroku <https://heroku.com>`__::
# example Tynd Disque connection
import os
Q_CLUSTER = {
'name': 'TyndBroker',
'workers': 8,
'timeout': 30,
'retry': 60,
'bulk': 10,
'disque_nodes': os.environ['TYND_DISQUE_NODES'].split(','),
'disque_auth': os.environ['TYND_DISQUE_AUTH']
}
disque_auth
~~~~~~~~~~~
Optional Disque password for servers that require authentication.
.. _ironmq_configuration:
iron_mq
+1 -1
View File
@@ -23,7 +23,7 @@ Features
- Django Admin integration
- PaaS compatible with multiple instances
- Multi cluster monitor
- Redis, Disque, IronMQ, SQS, MongoDB or ORM
- Redis, IronMQ, SQS, MongoDB or ORM
- Rollbar and Sentry support
+3 -9
View File
@@ -32,16 +32,12 @@ Django Q2 is tested for Python 3.7, 3.8, 3.9 and 3.10
- `Django <https://www.djangoproject.com>`__
Django Q2 aims to use as much of Django's standard offerings as possible.
The code is tested against Django versions `2.2.x` and `3.2.x`.
The code is tested against Django versions `3.2.x` and `4.1.x`.
- `Django-picklefield <https://github.com/gintas/django-picklefield>`__
Used to store args, kwargs and result objects in the database.
- `Arrow <https://github.com/crsmithdev/arrow>`__
The scheduler uses Chris Smith's wonderful project to determine correct dates in the future.
- `Blessed <https://github.com/jquast/blessed>`__
This feature-filled fork of Erik Rose's blessings project provides the terminal layout of the monitor.
@@ -49,7 +45,7 @@ Django Q2 is tested for Python 3.7, 3.8, 3.9 and 3.10
Optional
~~~~~~~~
- `Redis-py <https://github.com/andymccurdy/redis-py>`__ client by Andy McCurdy is used to interface with both the Redis and Disque brokers::
- `Redis-py <https://github.com/andymccurdy/redis-py>`__ client by Andy McCurdy is used to interface with both the Redis::
$ pip install redis
@@ -77,8 +73,6 @@ Optional
- `Redis <http://redis.io/>`__ server is the default broker for Django Q2. It provides the best performance and does not require Django's cache framework for monitoring.
- `Disque <https://github.com/antirez/disque>`__ server is based on Redis by the same author, but focuses on reliable queues. Currently in Alpha, but highly recommended. You can either build it from source or use it on Heroku through the `Tynd <https://disque.tynd.co/>`__ beta.
- `MongoDB <https://www.mongodb.org/>`__ is a highly scalable NoSQL database which makes for a very fast and reliably persistent at-least-once message broker. Usually available on most PaaS providers.
- `Pyrollbar <https://github.com/rollbar/pyrollbar>`__ is an error notifier for `Rollbar <https://rollbar.com/>`__ which lets you manage your worker errors in one place. Needs a `Rollbar <https://rollbar.com/>`__ account and access key::
@@ -142,7 +136,7 @@ You can reference the `requirements <https://github.com/GDay/django-q2/blob/mast
Django
~~~~~~
We strive to be compatible with last two major version of Django.
At the moment this means we support the 2.2.x and 3.2.x releases.
At the moment this means we support the 3.2.x and 4.1.x releases.
Since we are now no longer supporting Python 2, we can also not support older versions of Django that do not support Python >= 3.6
For this you can always use older releases, but they are no longer maintained.
+2 -2
View File
@@ -37,14 +37,14 @@ You can manage them through the :ref:`admin_page` or directly from your code wit
# Run a schedule every 5 minutes, starting at 6 today
# for 2 hours
import arrow
from datetime import datetime
schedule('math.hypot',
3, 4,
schedule_type=Schedule.MINUTES,
minutes=5,
repeats=24,
next_run=arrow.utcnow().replace(hour=18, minute=0))
next_run=datetime.utcnow().replace(hour=18, minute=0))
# Use a cron expression
schedule('math.hypot',
Generated
+539 -482
View File
File diff suppressed because it is too large Load Diff
+13 -14
View File
@@ -44,20 +44,19 @@ include = ['CHANGELOG.md']
[tool.poetry.dependencies]
python = ">=3.7.13, <4"
django = ">=2.2"
blessed = "^1.17.6"
arrow = "^1.1.0"
django-picklefield = "^3.0.1"
python = ">=3.8.14, <4"
django = ">=3.2"
blessed = "^1.19.1"
django-picklefield = "^3.1"
hiredis = { version = "^1.0.1", optional = true }
redis = { version = "^3.5.3", optional = true }
psutil = { version = "^5.7.0", optional = true }
django-redis = { version = "^4.12.1", optional = true }
hiredis = { version = "^2.0.0", optional = true }
redis = { version = "^4.3.4", optional = true }
psutil = { version = "^5.9.2", optional = true }
django-redis = { version = "^5.2.0", optional = true }
iron-mq = { version = "^0.9", optional = true }
boto3 = { version = "^1.14.12", optional = true }
pymongo = { version = "^3.10.1", optional = true }
croniter = { version = "^0.3.34", optional = true }
boto3 = { version = "^1.24.92", optional = true }
pymongo = { version = "^4.2.0", optional = true }
croniter = { version = "^1.3.7", optional = true }
django-q-rollbar = {version = ">=0.1", optional = true}
django-q-sentry = {version = ">=0.1", optional = true}
@@ -66,8 +65,8 @@ django-q-sentry = {version = ">=0.1", optional = true}
pytest = "^7.1.3"
pytest-django = "^4.5.2"
Sphinx = "^4.0.2"
pytest-cov = "^3.0.0"
black = "^22.8.0"
pytest-cov = "^4.0.0"
black = "^22.10.0"
isort = {extras = ["requirements_deprecated_finder"], version = "^5.10.1"}
[tool.poetry.extras]
+25 -11
View File
@@ -1,17 +1,31 @@
version: '2.2'
version: '3'
services:
disque:
image: efrecon/disque:1.0-rc1
ports:
- '7711:7711/tcp'
redis:
image: redis:latest
ports:
- '6379:6379/tcp'
expose:
- '6379/tcp'
networks:
- main
mongo:
image: mongo:4
ports:
- '27017:27017/tcp'
image: mongo:6
expose:
- '27017/tcp'
networks:
- main
django-q2:
build:
dockerfile: ./Dockerfile.dev
context: .
volumes:
- .:/app
depends_on:
- redis
- mongo
networks:
- main
networks:
main: