Build improvements (#569)

* Black linting

* Adding Black to dev dependencies and upping minimal python to 3.6.2 for compatibility

* Updating packages

* Removing pip-tools input and exporting requirements with poetry

* Deleting old setup files and test runner

* Trying 1.3.7

* Looser extras requirements to prevent conflicts

* Sorted imports with isort

* Added iSort to dev dependencies

* Fixes localtime for naive setups
This commit is contained in:
Ilan Steemers
2021-05-30 17:10:07 +02:00
committed by GitHub
parent dc95da3a6b
commit 15155c7a99
45 changed files with 1497 additions and 4194 deletions
-9
View File
@@ -1,9 +0,0 @@
include LICENSE
include README.rst
include CHANGELOG.
recursive-include django_q/locale *
include django_q/management/*.py
include django_q/management/commands/*.py
include django_q/migrations/*.py
include django_q/brokers/*.py
include django_q/tests/*.py
+1 -1
View File
@@ -1,4 +1,4 @@
VERSION = (1, 3, 6) VERSION = (1, 3, 7)
default_app_config = "django_q.apps.DjangoQConfig" default_app_config = "django_q.apps.DjangoQConfig"
+4 -4
View File
@@ -3,7 +3,7 @@ from django.contrib import admin
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django_q.conf import Conf, croniter from django_q.conf import Conf, croniter
from django_q.models import Success, Failure, Schedule, OrmQ from django_q.models import Failure, OrmQ, Schedule, Success
from django_q.tasks import async_task from django_q.tasks import async_task
@@ -60,7 +60,7 @@ class FailAdmin(admin.ModelAdmin):
class ScheduleAdmin(admin.ModelAdmin): class ScheduleAdmin(admin.ModelAdmin):
""" model admin for schedules """ """model admin for schedules"""
list_display = ( list_display = (
"id", "id",
@@ -84,7 +84,7 @@ class ScheduleAdmin(admin.ModelAdmin):
class QueueAdmin(admin.ModelAdmin): class QueueAdmin(admin.ModelAdmin):
""" queue admin for ORM broker """ """queue admin for ORM broker"""
list_display = ("id", "key", "task_id", "name", "func", "lock") list_display = ("id", "key", "task_id", "name", "func", "lock")
@@ -100,7 +100,7 @@ class QueueAdmin(admin.ModelAdmin):
def has_add_permission(self, request): def has_add_permission(self, request):
"""Don't allow adds.""" """Don't allow adds."""
return False return False
list_filter = ("key",) list_filter = ("key",)
+1 -1
View File
@@ -1,7 +1,7 @@
import importlib import importlib
from typing import Optional from typing import Optional
from django.core.cache import caches, InvalidCacheBackendError from django.core.cache import InvalidCacheBackendError, caches
from django_q.conf import Conf from django_q.conf import Conf
+4 -2
View File
@@ -38,7 +38,9 @@ class Sqs(Broker):
if not isinstance(wait_time_second, int): if not isinstance(wait_time_second, int):
raise ValueError("receive_message_wait_time_seconds should be int") raise ValueError("receive_message_wait_time_seconds should be int")
if wait_time_second > 20: if wait_time_second > 20:
raise ValueError("receive_message_wait_time_seconds is invalid. Reason: Must be >= 0 and <= 20") raise ValueError(
"receive_message_wait_time_seconds is invalid. Reason: Must be >= 0 and <= 20"
)
params.update({"WaitTimeSeconds": wait_time_second}) params.update({"WaitTimeSeconds": wait_time_second})
tasks = self.queue.receive_messages(**params) tasks = self.queue.receive_messages(**params)
@@ -80,7 +82,7 @@ class Sqs(Broker):
config["region_name"] = config["aws_region"] config["region_name"] = config["aws_region"]
del config["aws_region"] del config["aws_region"]
if 'receive_message_wait_time_seconds' in config: if "receive_message_wait_time_seconds" in config:
del config["receive_message_wait_time_seconds"] del config["receive_message_wait_time_seconds"]
return Session(**config) return Session(**config)
+2 -1
View File
@@ -2,10 +2,11 @@ import random
# External # External
import redis import redis
from redis import Redis
# Django # Django
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from redis import Redis
from django_q.brokers import Broker from django_q.brokers import Broker
from django_q.conf import Conf from django_q.conf import Conf
+30 -16
View File
@@ -6,6 +6,7 @@ import signal
import socket import socket
import traceback import traceback
import uuid import uuid
from datetime import datetime
from multiprocessing import Event, Process, Value, current_process from multiprocessing import Event, Process, Value, current_process
from time import sleep from time import sleep
@@ -13,13 +14,14 @@ from time import sleep
import arrow import arrow
# Django # Django
from django import db, core from django import core, db
from django.apps.registry import apps from django.apps.registry import apps
try: try:
apps.check_apps_ready() apps.check_apps_ready()
except core.exceptions.AppRegistryNotReady: except core.exceptions.AppRegistryNotReady:
import django import django
django.setup() django.setup()
from django.conf import settings from django.conf import settings
@@ -28,21 +30,21 @@ from django.utils.translation import gettext_lazy as _
# Local # Local
import django_q.tasks import django_q.tasks
from django_q.brokers import get_broker, Broker from django_q.brokers import Broker, get_broker
from django_q.conf import ( from django_q.conf import (
Conf, Conf,
croniter,
error_reporter,
get_ppid,
logger, logger,
psutil, psutil,
get_ppid,
error_reporter,
croniter,
resource, resource,
) )
from django_q.humanhash import humanize from django_q.humanhash import humanize
from django_q.models import Task, Success, Schedule from django_q.models import Schedule, Success, Task
from django_q.queues import Queue from django_q.queues import Queue
from django_q.signals import pre_execute from django_q.signals import pre_execute
from django_q.signing import SignedPackage, BadSignature from django_q.signing import BadSignature, SignedPackage
from django_q.status import Stat, Status from django_q.status import Stat, Status
@@ -485,8 +487,11 @@ def save_task(task, broker: Broker):
existing_task.attempt_count = existing_task.attempt_count + 1 existing_task.attempt_count = existing_task.attempt_count + 1
existing_task.save() existing_task.save()
if Conf.MAX_ATTEMPTS > 0 and existing_task.attempt_count >= Conf.MAX_ATTEMPTS: if (
broker.acknowledge(task['ack_id']) Conf.MAX_ATTEMPTS > 0
and existing_task.attempt_count >= Conf.MAX_ATTEMPTS
):
broker.acknowledge(task["ack_id"])
else: else:
func = task["func"] func = task["func"]
@@ -495,8 +500,8 @@ def save_task(task, broker: Broker):
func = f"{func.__module__}.{func.__name__}" func = f"{func.__module__}.{func.__name__}"
elif inspect.ismethod(func): elif inspect.ismethod(func):
func = ( func = (
f'{func.__self__.__module__}.' f"{func.__self__.__module__}."
f'{func.__self__.__name__}.{func.__name__}' f"{func.__self__.__name__}.{func.__name__}"
) )
Task.objects.create( Task.objects.create(
id=task["id"], id=task["id"],
@@ -510,7 +515,7 @@ def save_task(task, broker: Broker):
result=task["result"], result=task["result"],
group=task.get("group"), group=task.get("group"),
success=task["success"], success=task["success"],
attempt_count=1 attempt_count=1,
) )
except Exception as e: except Exception as e:
logger.error(e) logger.error(e)
@@ -582,7 +587,9 @@ def scheduler(broker: Broker = None):
Schedule.objects.select_for_update() Schedule.objects.select_for_update()
.exclude(repeats=0) .exclude(repeats=0)
.filter(next_run__lt=timezone.now()) .filter(next_run__lt=timezone.now())
.filter(db.models.Q(cluster__isnull=True) | db.models.Q(cluster=Conf.PREFIX)) .filter(
db.models.Q(cluster__isnull=True) | db.models.Q(cluster=Conf.PREFIX)
)
): ):
args = () args = ()
kwargs = {} kwargs = {}
@@ -627,7 +634,7 @@ def scheduler(broker: Broker = None):
) )
) )
next_run = arrow.get( next_run = arrow.get(
croniter(s.cron, timezone.localtime()).get_next() croniter(s.cron, localtime()).get_next()
) )
if Conf.CATCH_UP or next_run > arrow.utcnow(): if Conf.CATCH_UP or next_run > arrow.utcnow():
break break
@@ -643,7 +650,7 @@ def scheduler(broker: Broker = None):
scheduled_broker = broker scheduled_broker = broker
try: try:
scheduled_broker = get_broker(q_options["broker_name"]) scheduled_broker = get_broker(q_options["broker_name"])
except: # invalid broker_name or non existing broker with broker_name except: # invalid broker_name or non existing broker with broker_name
pass pass
q_options["broker"] = scheduled_broker q_options["broker"] = scheduled_broker
q_options["group"] = q_options.get("group", s.name or s.id) q_options["group"] = q_options.get("group", s.name or s.id)
@@ -734,4 +741,11 @@ def rss_check():
return resource.getrusage(resource.RUSAGE_SELF).ru_maxrss >= Conf.MAX_RSS return resource.getrusage(resource.RUSAGE_SELF).ru_maxrss >= Conf.MAX_RSS
elif psutil: elif psutil:
return psutil.Process().memory_info().rss >= Conf.MAX_RSS * 1024 return psutil.Process().memory_info().rss >= Conf.MAX_RSS * 1024
return False return False
def localtime() -> datetime:
"""" Override for timezone.localtime to deal with naive times and local times"""
if settings.USE_TZ:
return timezone.localtime()
return datetime.now()
+9 -7
View File
@@ -135,12 +135,13 @@ class Conf:
RETRY = conf.get("retry", 60) RETRY = conf.get("retry", 60)
# Verify if retry and timeout settings are correct # Verify if retry and timeout settings are correct
if not TIMEOUT or (TIMEOUT > RETRY): if not TIMEOUT or (TIMEOUT > RETRY):
warn("""Retry and timeout are misconfigured. Set retry larger than timeout, warn(
"""Retry and timeout are misconfigured. Set retry larger than timeout,
failure to do so will cause the tasks to be retriggered before completion. failure to do so will cause the tasks to be retriggered before completion.
See https://django-q.readthedocs.io/en/latest/configure.html#retry for details.""") See https://django-q.readthedocs.io/en/latest/configure.html#retry for details."""
)
# Sets the amount of tasks the cluster will try to pop off the broker. # Sets the amount of tasks the cluster will try to pop off the broker.
# If it supports bulk gets. # If it supports bulk gets.
BULK = conf.get("bulk", 1) BULK = conf.get("bulk", 1)
@@ -176,7 +177,7 @@ class Conf:
ERROR_REPORTER = conf.get("error_reporter", {}) ERROR_REPORTER = conf.get("error_reporter", {})
# Optional attempt count. set to 0 for infinite attempts # Optional attempt count. set to 0 for infinite attempts
MAX_ATTEMPTS = conf.get('max_attempts', 0) MAX_ATTEMPTS = conf.get("max_attempts", 0)
# OSX doesn't implement qsize because of missing sem_getvalue() # OSX doesn't implement qsize because of missing sem_getvalue()
try: try:
@@ -200,7 +201,8 @@ class Conf:
# to manage workarounds during testing # to manage workarounds during testing
TESTING = conf.get("testing", False) TESTING = conf.get("testing", False)
# logger # logger
logger = logging.getLogger("django-q") logger = logging.getLogger("django-q")
+4 -9
View File
@@ -2,15 +2,10 @@ import datetime
import time import time
import zlib import zlib
from django.core.signing import ( from django.core.signing import BadSignature, JSONSerializer, SignatureExpired
BadSignature, from django.core.signing import Signer as Sgnr
SignatureExpired, from django.core.signing import TimestampSigner as TsS
b64_decode, from django.core.signing import b64_decode, dumps
JSONSerializer,
Signer as Sgnr,
TimestampSigner as TsS,
dumps,
)
from django.utils import baseconv from django.utils import baseconv
from django.utils.crypto import constant_time_compare from django.utils.crypto import constant_time_compare
from django.utils.encoding import force_bytes, force_str from django.utils.encoding import force_bytes, force_str
+266 -45
View File
@@ -4,50 +4,269 @@ 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`.
""" """
from argparse import ArgumentError
import operator import operator
import uuid as uuidlib import uuid as uuidlib
from argparse import ArgumentError
from functools import reduce from functools import reduce
DEFAULT_WORDLIST = ( DEFAULT_WORDLIST = (
'ack', 'alabama', 'alanine', 'alaska', 'alpha', 'angel', 'apart', 'april', "ack",
'arizona', 'arkansas', 'artist', 'asparagus', 'aspen', 'august', 'autumn', "alabama",
'avocado', 'bacon', 'bakerloo', 'batman', 'beer', 'berlin', 'beryllium', "alanine",
'black', 'blossom', 'blue', 'bluebird', 'bravo', 'bulldog', 'burger', "alaska",
'butter', 'california', 'carbon', 'cardinal', 'carolina', 'carpet', 'cat', "alpha",
'ceiling', 'charlie', 'chicken', 'coffee', 'cola', 'cold', 'colorado', "angel",
'comet', 'connecticut', 'crazy', 'cup', 'dakota', 'december', 'delaware', "apart",
'delta', 'diet', 'don', 'double', 'early', 'earth', 'east', 'echo', "april",
'edward', 'eight', 'eighteen', 'eleven', 'emma', 'enemy', 'equal', "arizona",
'failed', 'fanta', 'fifteen', 'fillet', 'finch', 'fish', 'five', 'fix', "arkansas",
'floor', 'florida', 'football', 'four', 'fourteen', 'foxtrot', 'freddie', "artist",
'friend', 'fruit', 'gee', 'georgia', 'glucose', 'golf', 'green', 'grey', "asparagus",
'hamper', 'happy', 'harry', 'hawaii', 'helium', 'high', 'hot', 'hotel', "aspen",
'hydrogen', 'idaho', 'illinois', 'india', 'indigo', 'ink', 'iowa', "august",
'island', 'item', 'jersey', 'jig', 'johnny', 'juliet', 'july', 'jupiter', "autumn",
'kansas', 'kentucky', 'kilo', 'king', 'kitten', 'lactose', 'lake', 'lamp', "avocado",
'lemon', 'leopard', 'lima', 'lion', 'lithium', 'london', 'louisiana', "bacon",
'low', 'magazine', 'magnesium', 'maine', 'mango', 'march', 'mars', "bakerloo",
'maryland', 'massachusetts', 'may', 'mexico', 'michigan', 'mike', "batman",
'minnesota', 'mirror', 'mississippi', 'missouri', 'mobile', 'mockingbird', "beer",
'monkey', 'montana', 'moon', 'mountain', 'muppet', 'music', 'nebraska', "berlin",
'neptune', 'network', 'nevada', 'nine', 'nineteen', 'nitrogen', 'north', "beryllium",
'november', 'nuts', 'october', 'ohio', 'oklahoma', 'one', 'orange', "black",
'oranges', 'oregon', 'oscar', 'oven', 'oxygen', 'papa', 'paris', 'pasta', "blossom",
'pennsylvania', 'pip', 'pizza', 'pluto', 'potato', 'princess', 'purple', "blue",
'quebec', 'queen', 'quiet', 'red', 'river', 'robert', 'robin', 'romeo', "bluebird",
'rugby', 'sad', 'salami', 'saturn', 'september', 'seven', 'seventeen', "bravo",
'shade', 'sierra', 'single', 'sink', 'six', 'sixteen', 'skylark', 'snake', "bulldog",
'social', 'sodium', 'solar', 'south', 'spaghetti', 'speaker', 'spring', "burger",
'stairway', 'steak', 'stream', 'summer', 'sweet', 'table', 'tango', 'ten', "butter",
'tennessee', 'tennis', 'texas', 'thirteen', 'three', 'timing', 'triple', "california",
'twelve', 'twenty', 'two', 'uncle', 'undress', 'uniform', 'uranus', 'utah', "carbon",
'vegan', 'venus', 'vermont', 'victor', 'video', 'violet', 'virginia', "cardinal",
'washington', 'west', 'whiskey', 'white', 'william', 'winner', 'winter', "carolina",
'wisconsin', 'wolfram', 'wyoming', 'xray', 'yankee', 'yellow', 'zebra', "carpet",
'zulu') "cat",
"ceiling",
"charlie",
"chicken",
"coffee",
"cola",
"cold",
"colorado",
"comet",
"connecticut",
"crazy",
"cup",
"dakota",
"december",
"delaware",
"delta",
"diet",
"don",
"double",
"early",
"earth",
"east",
"echo",
"edward",
"eight",
"eighteen",
"eleven",
"emma",
"enemy",
"equal",
"failed",
"fanta",
"fifteen",
"fillet",
"finch",
"fish",
"five",
"fix",
"floor",
"florida",
"football",
"four",
"fourteen",
"foxtrot",
"freddie",
"friend",
"fruit",
"gee",
"georgia",
"glucose",
"golf",
"green",
"grey",
"hamper",
"happy",
"harry",
"hawaii",
"helium",
"high",
"hot",
"hotel",
"hydrogen",
"idaho",
"illinois",
"india",
"indigo",
"ink",
"iowa",
"island",
"item",
"jersey",
"jig",
"johnny",
"juliet",
"july",
"jupiter",
"kansas",
"kentucky",
"kilo",
"king",
"kitten",
"lactose",
"lake",
"lamp",
"lemon",
"leopard",
"lima",
"lion",
"lithium",
"london",
"louisiana",
"low",
"magazine",
"magnesium",
"maine",
"mango",
"march",
"mars",
"maryland",
"massachusetts",
"may",
"mexico",
"michigan",
"mike",
"minnesota",
"mirror",
"mississippi",
"missouri",
"mobile",
"mockingbird",
"monkey",
"montana",
"moon",
"mountain",
"muppet",
"music",
"nebraska",
"neptune",
"network",
"nevada",
"nine",
"nineteen",
"nitrogen",
"north",
"november",
"nuts",
"october",
"ohio",
"oklahoma",
"one",
"orange",
"oranges",
"oregon",
"oscar",
"oven",
"oxygen",
"papa",
"paris",
"pasta",
"pennsylvania",
"pip",
"pizza",
"pluto",
"potato",
"princess",
"purple",
"quebec",
"queen",
"quiet",
"red",
"river",
"robert",
"robin",
"romeo",
"rugby",
"sad",
"salami",
"saturn",
"september",
"seven",
"seventeen",
"shade",
"sierra",
"single",
"sink",
"six",
"sixteen",
"skylark",
"snake",
"social",
"sodium",
"solar",
"south",
"spaghetti",
"speaker",
"spring",
"stairway",
"steak",
"stream",
"summer",
"sweet",
"table",
"tango",
"ten",
"tennessee",
"tennis",
"texas",
"thirteen",
"three",
"timing",
"triple",
"twelve",
"twenty",
"two",
"uncle",
"undress",
"uniform",
"uranus",
"utah",
"vegan",
"venus",
"vermont",
"victor",
"video",
"violet",
"virginia",
"washington",
"west",
"whiskey",
"white",
"william",
"winner",
"winter",
"wisconsin",
"wolfram",
"wyoming",
"xray",
"yankee",
"yellow",
"zebra",
"zulu",
)
class HumanHasher: class HumanHasher:
@@ -70,7 +289,7 @@ class HumanHasher:
raise ArgumentError("Wordlist must have exactly 256 items") raise ArgumentError("Wordlist must have exactly 256 items")
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.
@@ -84,7 +303,10 @@ class HumanHasher:
""" """
# Gets a list of byte values between 0-255. # Gets a list of byte values between 0-255.
bytes = [int(x, 16) for x in list(map(''.join, list(zip(hexdigest[::2], hexdigest[1::2]))))] bytes = [
int(x, 16)
for x in list(map("".join, list(zip(hexdigest[::2], hexdigest[1::2]))))
]
# Compress an arbitrary number of bytes to `words`. # Compress an arbitrary number of bytes to `words`.
compressed = self.compress(bytes, words) compressed = self.compress(bytes, words)
# Map the compressed byte values through the word list. # Map the compressed byte values through the word list.
@@ -115,10 +337,9 @@ class HumanHasher:
# Split `bytes` into `target` segments. # Split `bytes` into `target` segments.
seg_size = length // target seg_size = length // target
segments = [bytes[i * seg_size:(i + 1) * seg_size] segments = [bytes[i * seg_size : (i + 1) * seg_size] for i in range(target)]
for i in range(target)]
# Catch any left-over bytes in the last segment. # Catch any left-over bytes in the last segment.
segments[-1].extend(bytes[target * seg_size:]) segments[-1].extend(bytes[target * seg_size :])
# Use a simple XOR checksum-like function for compression. # Use a simple XOR checksum-like function for compression.
checksum = lambda bytes: reduce(operator.xor, bytes, 0) checksum = lambda bytes: reduce(operator.xor, bytes, 0)
@@ -134,7 +355,7 @@ class HumanHasher:
as :meth:`humanize` (they'll be passed straight through). as :meth:`humanize` (they'll be passed straight through).
""" """
digest = str(uuidlib.uuid4()).replace('-', '') digest = str(uuidlib.uuid4()).replace("-", "")
return self.humanize(digest, **params), digest return self.humanize(digest, **params), digest
+5 -5
View File
@@ -10,15 +10,15 @@ class Command(BaseCommand):
def add_arguments(self, parser): def add_arguments(self, parser):
parser.add_argument( parser.add_argument(
'--run-once', "--run-once",
action='store_true', action="store_true",
dest='run_once', dest="run_once",
default=False, default=False,
help='Run once and then stop.', help="Run once and then stop.",
) )
def handle(self, *args, **options): def handle(self, *args, **options):
q = Cluster() q = Cluster()
q.start() q.start()
if options.get('run_once', False): if options.get("run_once", False):
q.stop() q.stop()
+1 -1
View File
@@ -3,7 +3,7 @@ from django.utils.translation import gettext as _
from django_q import VERSION from django_q import VERSION
from django_q.conf import Conf from django_q.conf import Conf
from django_q.monitor import info, get_ids from django_q.monitor import get_ids, info
class Command(BaseCommand): class Command(BaseCommand):
+1 -1
View File
@@ -27,5 +27,5 @@ class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
memory( memory(
run_once=options.get("run_once", False), run_once=options.get("run_once", False),
workers=options.get("workers", False) workers=options.get("workers", False),
) )
+2 -2
View File
@@ -1,6 +1,6 @@
from django.db import models, migrations
import picklefield.fields
import django.utils.timezone import django.utils.timezone
import picklefield.fields
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
@@ -1,4 +1,4 @@
from django.db import models, migrations from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
@@ -1,4 +1,4 @@
from django.db import models, migrations from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
@@ -1,4 +1,4 @@
from django.db import models, migrations from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
@@ -1,4 +1,4 @@
from django.db import models, migrations from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
@@ -1,4 +1,4 @@
from django.db import models, migrations from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
+1 -1
View File
@@ -1,4 +1,4 @@
from django.db import models, migrations from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
@@ -1,5 +1,5 @@
from django.db import migrations
import picklefield.fields import picklefield.fields
from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
@@ -1,6 +1,7 @@
# Generated by Django 3.0.8 on 2020-07-02 16:08 # Generated by Django 3.0.8 on 2020-07-02 16:08
from django.db import migrations, models from django.db import migrations, models
import django_q.models import django_q.models
+58 -18
View File
@@ -5,15 +5,16 @@ from blessed import Terminal
# django # django
from django.db import connection from django.db import connection
from django.db.models import Sum, F from django.db.models import F, Sum
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django_q import VERSION, models
from django_q.brokers import get_broker
# local # local
from django_q.conf import Conf from django_q.conf import Conf
from django_q.status import Stat from django_q.status import Stat
from django_q.brokers import get_broker
from django_q import models, VERSION
# optional # optional
try: try:
@@ -27,7 +28,7 @@ def get_process_mb(pid):
process = psutil.Process(pid) process = psutil.Process(pid)
mb_used = round(process.memory_info().rss / 1024 ** 2, 2) mb_used = round(process.memory_info().rss / 1024 ** 2, 2)
except psutil.NoSuchProcess: except psutil.NoSuchProcess:
mb_used = 'NO_PROCESS_FOUND' mb_used = "NO_PROCESS_FOUND"
return mb_used return mb_used
@@ -39,7 +40,10 @@ def monitor(run_once=False, broker=None):
with term.fullscreen(), term.hidden_cursor(), term.cbreak(): with term.fullscreen(), term.hidden_cursor(), term.cbreak():
val = None val = None
start_width = int(term.width / 8) start_width = int(term.width / 8)
while val not in ("q", "Q",): while val not in (
"q",
"Q",
):
col_width = int(term.width / 8) col_width = int(term.width / 8)
# In case of resize # In case of resize
if col_width != start_width: if col_width != start_width:
@@ -294,7 +298,11 @@ def memory(run_once=False, workers=False, broker=None):
broker.ping() broker.ping()
if not psutil: if not psutil:
print(term.clear_eos()) print(term.clear_eos())
print(term.white_on_red("Cannot start \"qmemory\" command. Missing \"psutil\" library.")) print(
term.white_on_red(
'Cannot start "qmemory" command. Missing "psutil" library.'
)
)
return return
with term.fullscreen(), term.hidden_cursor(), term.cbreak(): with term.fullscreen(), term.hidden_cursor(), term.cbreak():
MEMORY_AVAILABLE_LOWEST_PERCENTAGE = 100.0 MEMORY_AVAILABLE_LOWEST_PERCENTAGE = 100.0
@@ -319,11 +327,15 @@ def memory(run_once=False, workers=False, broker=None):
) )
print( print(
term.move(0, 2 * col_width) term.move(0, 2 * col_width)
+ term.black_on_green(term.center(_("Available (%)"), width=col_width - 1)) + term.black_on_green(
term.center(_("Available (%)"), width=col_width - 1)
)
) )
print( print(
term.move(0, 3 * col_width) term.move(0, 3 * col_width)
+ term.black_on_green(term.center(_("Available (MB)"), width=col_width - 1)) + term.black_on_green(
term.center(_("Available (MB)"), width=col_width - 1)
)
) )
print( print(
term.move(0, 4 * col_width) term.move(0, 4 * col_width)
@@ -331,24 +343,37 @@ def memory(run_once=False, workers=False, broker=None):
) )
print( print(
term.move(0, 5 * col_width) term.move(0, 5 * col_width)
+ term.black_on_green(term.center(_("Sentinel (MB)"), width=col_width - 1)) + term.black_on_green(
term.center(_("Sentinel (MB)"), width=col_width - 1)
)
) )
print( print(
term.move(0, 6 * col_width) term.move(0, 6 * col_width)
+ term.black_on_green(term.center(_("Monitor (MB)"), width=col_width - 1)) + term.black_on_green(
term.center(_("Monitor (MB)"), width=col_width - 1)
)
) )
print( print(
term.move(0, 7 * col_width) term.move(0, 7 * col_width)
+ term.black_on_green(term.center(_("Workers (MB)"), width=col_width - 1)) + term.black_on_green(
term.center(_("Workers (MB)"), width=col_width - 1)
)
) )
row = 2 row = 2
stats = Stat.get_all(broker=broker) stats = Stat.get_all(broker=broker)
print(term.clear_eos()) print(term.clear_eos())
for stat in stats: for stat in stats:
# memory available (%) # memory available (%)
memory_available_percentage = round(psutil.virtual_memory().available * 100 / psutil.virtual_memory().total, 2) memory_available_percentage = round(
psutil.virtual_memory().available
* 100
/ psutil.virtual_memory().total,
2,
)
# memory available (MB) # memory available (MB)
memory_available = round(psutil.virtual_memory().available / 1024 ** 2, 2) memory_available = round(
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()
@@ -370,7 +395,10 @@ def memory(run_once=False, workers=False, broker=None):
) )
print( print(
term.move(row, 4 * col_width) term.move(row, 4 * col_width)
+ term.center(round(psutil.virtual_memory().total / 1024 ** 2, 2), width=col_width - 1) + term.center(
round(psutil.virtual_memory().total / 1024 ** 2, 2),
width=col_width - 1,
)
) )
print( print(
term.move(row, 5 * col_width) term.move(row, 5 * col_width)
@@ -378,7 +406,10 @@ def memory(run_once=False, workers=False, broker=None):
) )
print( print(
term.move(row, 6 * col_width) term.move(row, 6 * col_width)
+ term.center(get_process_mb(getattr(stat, 'monitor', None)), width=col_width - 1) + term.center(
get_process_mb(getattr(stat, "monitor", None)),
width=col_width - 1,
)
) )
workers_mb = 0 workers_mb = 0
for worker_pid in stat.workers: for worker_pid in stat.workers:
@@ -388,7 +419,9 @@ def memory(run_once=False, workers=False, broker=None):
workers_mb += result workers_mb += result
print( print(
term.move(row, 7 * col_width) term.move(row, 7 * col_width)
+ term.center(workers_mb or 'NO_PROCESSES_FOUND', width=col_width - 1) + term.center(
workers_mb or "NO_PROCESSES_FOUND", width=col_width - 1
)
) )
row += 1 row += 1
# each worker's memory usage # each worker's memory usage
@@ -402,7 +435,12 @@ def memory(run_once=False, workers=False, broker=None):
for worker_num in range(Conf.WORKERS): for worker_num in range(Conf.WORKERS):
print( print(
term.move(row, (worker_num + 1) * col_width) term.move(row, (worker_num + 1) * col_width)
+ term.black_on_cyan(term.center("Worker #{} (MB)".format(worker_num + 1), width=col_width - 1)) + term.black_on_cyan(
term.center(
"Worker #{} (MB)".format(worker_num + 1),
width=col_width - 1,
)
)
) )
row += 2 row += 2
for stat in stats: for stat in stats:
@@ -422,7 +460,9 @@ def memory(run_once=False, workers=False, broker=None):
term.move(row, 0) term.move(row, 0)
+ _("Available lowest (%): {} ({})").format( + _("Available lowest (%): {} ({})").format(
str(MEMORY_AVAILABLE_LOWEST_PERCENTAGE), str(MEMORY_AVAILABLE_LOWEST_PERCENTAGE),
MEMORY_AVAILABLE_LOWEST_PERCENTAGE_AT.strftime('%Y-%m-%d %H:%M:%S+00:00') MEMORY_AVAILABLE_LOWEST_PERCENTAGE_AT.strftime(
"%Y-%m-%d %H:%M:%S+00:00"
),
) )
) )
# for testing # for testing
+7 -7
View File
@@ -7,7 +7,7 @@ import sys
class SharedCounter: class SharedCounter:
""" A synchronized shared counter. """A synchronized shared counter.
The locking done by multiprocessing.Value ensures that only a single The locking done by multiprocessing.Value ensures that only a single
process or thread may read or write the in-memory ctypes object. However, process or thread may read or write the in-memory ctypes object. However,
@@ -24,18 +24,18 @@ class SharedCounter:
self.count = multiprocessing.Value("i", n) self.count = multiprocessing.Value("i", n)
def increment(self, n=1): def increment(self, n=1):
""" Increment the counter by n (default = 1) """ """Increment the counter by n (default = 1)"""
with self.count.get_lock(): with self.count.get_lock():
self.count.value += n self.count.value += n
@property @property
def value(self): def value(self):
""" Return the value of the counter """ """Return the value of the counter"""
return self.count.value return self.count.value
class Queue(multiprocessing.queues.Queue): class Queue(multiprocessing.queues.Queue):
""" A portable implementation of multiprocessing.Queue. """A portable implementation of multiprocessing.Queue.
Because of multithreading / multiprocessing semantics, Queue.qsize() may Because of multithreading / multiprocessing semantics, Queue.qsize() may
raise the NotImplementedError exception on Unix platforms like Mac OS X raise the NotImplementedError exception on Unix platforms like Mac OS X
@@ -57,7 +57,7 @@ class Queue(multiprocessing.queues.Queue):
self.size = SharedCounter(0) self.size = SharedCounter(0)
def __getstate__(self): def __getstate__(self):
return super(Queue, self).__getstate__() + (self.size, ) return super(Queue, self).__getstate__() + (self.size,)
def __setstate__(self, state): def __setstate__(self, state):
super(Queue, self).__setstate__(state[:-1]) super(Queue, self).__setstate__(state[:-1])
@@ -73,9 +73,9 @@ class Queue(multiprocessing.queues.Queue):
return x return x
def qsize(self) -> int: def qsize(self) -> int:
""" Reliable implementation of multiprocessing.Queue.qsize() """ """Reliable implementation of multiprocessing.Queue.qsize()"""
return self.size.value return self.size.value
def empty(self) -> bool: def empty(self) -> bool:
""" Reliable implementation of multiprocessing.Queue.empty() """ """Reliable implementation of multiprocessing.Queue.empty()"""
return not self.qsize() > 0 return not self.qsize() > 0
+2 -1
View File
@@ -1,7 +1,7 @@
import importlib import importlib
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.dispatch import receiver, Signal from django.dispatch import Signal, receiver
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django_q.conf import logger from django_q.conf import logger
@@ -31,6 +31,7 @@ def call_hook(sender, instance, **kwargs):
) )
) )
# args: task # args: task
pre_enqueue = Signal() pre_enqueue = Signal()
+2 -2
View File
@@ -3,9 +3,9 @@ from typing import Union
from django.utils import timezone from django.utils import timezone
from django_q.brokers import get_broker, Broker from django_q.brokers import Broker, get_broker
from django_q.conf import Conf, logger from django_q.conf import Conf, logger
from django_q.signing import SignedPackage, BadSignature from django_q.signing import BadSignature, SignedPackage
class Status: class Status:
+3 -3
View File
@@ -1,11 +1,11 @@
"""Provides task functionality.""" """Provides task functionality."""
# Standard # Standard
from multiprocessing import Value
from time import sleep, time from time import sleep, time
# django # django
from django.db import IntegrityError from django.db import IntegrityError
from django.utils import timezone from django.utils import timezone
from multiprocessing import Value
# local # local
from django_q.brokers import get_broker from django_q.brokers import get_broker
@@ -153,7 +153,7 @@ def result(task_id, wait=0, cached=Conf.CACHED):
def result_cached(task_id, wait=0, broker=None): def result_cached(task_id, wait=0, broker=None):
""" """
Return the result from the cache backend Return the result from the cache backend
""" """
if not broker: if not broker:
broker = get_broker() broker = get_broker()
@@ -755,7 +755,7 @@ class AsyncTask:
def _sync(pack): def _sync(pack):
"""Simulate a package travelling through the cluster.""" """Simulate a package travelling through the cluster."""
from django_q.cluster import worker, monitor from django_q.cluster import monitor, worker
task_queue = Queue() task_queue = Queue()
result_queue = Queue() result_queue = Queue()
+49 -46
View File
@@ -1,4 +1,5 @@
import os import os
import django import django
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -8,7 +9,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ # See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret! # SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = ')cqmpi+p@n&!u&fu@!m@9h&1bz9mwmstsahe)nf!ms+c$uc=x7' SECRET_KEY = ")cqmpi+p@n&!u&fu@!m@9h&1bz9mwmstsahe)nf!ms+c$uc=x7"
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = True
@@ -19,41 +20,41 @@ ALLOWED_HOSTS = []
# Application definition # Application definition
INSTALLED_APPS = ( INSTALLED_APPS = (
'django.contrib.admin', "django.contrib.admin",
'django.contrib.auth', "django.contrib.auth",
'django.contrib.contenttypes', "django.contrib.contenttypes",
'django.contrib.sessions', "django.contrib.sessions",
'django.contrib.messages', "django.contrib.messages",
'django.contrib.staticfiles', "django.contrib.staticfiles",
'django_q', "django_q",
'django_redis' "django_redis",
) )
MIDDLEWARE_CLASSES = ( MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware', "django.contrib.sessions.middleware.SessionMiddleware",
'django.middleware.common.CommonMiddleware', "django.middleware.common.CommonMiddleware",
'django.middleware.csrf.CsrfViewMiddleware', "django.middleware.csrf.CsrfViewMiddleware",
'django.contrib.auth.middleware.AuthenticationMiddleware', "django.contrib.auth.middleware.AuthenticationMiddleware",
'django.contrib.messages.middleware.MessageMiddleware', "django.contrib.messages.middleware.MessageMiddleware",
'django.middleware.clickjacking.XFrameOptionsMiddleware', "django.middleware.clickjacking.XFrameOptionsMiddleware",
) )
MIDDLEWARE = MIDDLEWARE_CLASSES MIDDLEWARE = MIDDLEWARE_CLASSES
ROOT_URLCONF = 'tests.urls' ROOT_URLCONF = "tests.urls"
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', "BACKEND": "django.template.backends.django.DjangoTemplates",
'DIRS': [], "DIRS": [],
'APP_DIRS': True, "APP_DIRS": True,
'OPTIONS': { "OPTIONS": {
'context_processors': [ "context_processors": [
'django.template.context_processors.debug', "django.template.context_processors.debug",
'django.template.context_processors.request', "django.template.context_processors.request",
'django.contrib.auth.context_processors.auth', "django.contrib.auth.context_processors.auth",
'django.contrib.messages.context_processors.messages', "django.contrib.messages.context_processors.messages",
], ],
}, },
}, },
@@ -64,9 +65,9 @@ TEMPLATES = [
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases # https://docs.djangoproject.com/en/2.2/ref/settings/#databases
DATABASES = { DATABASES = {
'default': { "default": {
'ENGINE': 'django.db.backends.sqlite3', "ENGINE": "django.db.backends.sqlite3",
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), "NAME": os.path.join(BASE_DIR, "db.sqlite3"),
} }
} }
@@ -74,9 +75,9 @@ DATABASES = {
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/2.2/topics/i18n/ # https://docs.djangoproject.com/en/2.2/topics/i18n/
LANGUAGE_CODE = 'en-us' LANGUAGE_CODE = "en-us"
TIME_ZONE = 'UTC' TIME_ZONE = "UTC"
USE_I18N = True USE_I18N = True
@@ -85,17 +86,17 @@ USE_L10N = True
USE_TZ = True USE_TZ = True
LOGGING = { LOGGING = {
'version': 1, "version": 1,
'disable_existing_loggers': False, "disable_existing_loggers": False,
'handlers': { "handlers": {
'console': { "console": {
'class': 'logging.StreamHandler', "class": "logging.StreamHandler",
}, },
}, },
'loggers': { "loggers": {
'django_q': { "django_q": {
'handlers': ['console'], "handlers": ["console"],
'level': 'INFO', "level": "INFO",
}, },
}, },
} }
@@ -103,7 +104,7 @@ LOGGING = {
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/ # https://docs.djangoproject.com/en/2.2/howto/static-files/
STATIC_URL = '/static/' STATIC_URL = "/static/"
# Django Redis # Django Redis
CACHES = { CACHES = {
@@ -113,13 +114,15 @@ CACHES = {
"OPTIONS": { "OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient", "CLIENT_CLASS": "django_redis.client.DefaultClient",
"PARSER_CLASS": "redis.connection.HiredisParser", "PARSER_CLASS": "redis.connection.HiredisParser",
} },
} }
} }
# Django Q specific # Django Q specific
Q_CLUSTER = {'name': 'django_q_test', Q_CLUSTER = {
'cpu_affinity': 1, "name": "django_q_test",
'testing': True, "cpu_affinity": 1,
'log_level': 'DEBUG', "testing": True,
'django_redis': 'default'} "log_level": "DEBUG",
"django_redis": "default",
}
+44 -38
View File
@@ -1,80 +1,86 @@
import pytest
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
import pytest
from django_q.tasks import schedule
from django_q.models import Task, Failure, OrmQ
from django_q.humanhash import uuid
from django_q.conf import Conf from django_q.conf import Conf
from django_q.humanhash import uuid
from django_q.models import Failure, OrmQ, Task
from django_q.signing import SignedPackage from django_q.signing import SignedPackage
from django_q.tasks import schedule
@pytest.mark.django_db @pytest.mark.django_db
def test_admin_views(admin_client, monkeypatch): def test_admin_views(admin_client, monkeypatch):
monkeypatch.setattr(Conf, 'ORM', 'default') monkeypatch.setattr(Conf, "ORM", "default")
s = schedule('schedule.test') s = schedule("schedule.test")
tag = uuid() tag = uuid()
f = Task.objects.create( f = Task.objects.create(
id=tag[1], id=tag[1],
name=tag[0], name=tag[0],
func='test.fail', func="test.fail",
started=timezone.now(), started=timezone.now(),
stopped=timezone.now(), stopped=timezone.now(),
success=False) success=False,
)
tag = uuid() tag = uuid()
t = Task.objects.create( t = Task.objects.create(
id=tag[1], id=tag[1],
name=tag[0], name=tag[0],
func='test.success', func="test.success",
started=timezone.now(), started=timezone.now(),
stopped=timezone.now(), stopped=timezone.now(),
success=True) success=True,
)
q = OrmQ.objects.create( q = OrmQ.objects.create(
key='test', key="test",
payload=SignedPackage.dumps({'id': 1, 'func': 'test', 'name': 'test'})) payload=SignedPackage.dumps({"id": 1, "func": "test", "name": "test"}),
)
admin_urls = ( admin_urls = (
# schedule # schedule
reverse('admin:django_q_schedule_changelist'), reverse("admin:django_q_schedule_changelist"),
reverse('admin:django_q_schedule_add'), reverse("admin:django_q_schedule_add"),
reverse('admin:django_q_schedule_change', args=(s.id,)), reverse("admin:django_q_schedule_change", args=(s.id,)),
reverse('admin:django_q_schedule_history', args=(s.id,)), reverse("admin:django_q_schedule_history", args=(s.id,)),
reverse('admin:django_q_schedule_delete', args=(s.id,)), reverse("admin:django_q_schedule_delete", args=(s.id,)),
# success # success
reverse('admin:django_q_success_changelist'), reverse("admin:django_q_success_changelist"),
reverse('admin:django_q_success_change', args=(t.id,)), reverse("admin:django_q_success_change", args=(t.id,)),
reverse('admin:django_q_success_history', args=(t.id,)), reverse("admin:django_q_success_history", args=(t.id,)),
reverse('admin:django_q_success_delete', args=(t.id,)), reverse("admin:django_q_success_delete", args=(t.id,)),
# failure # failure
reverse('admin:django_q_failure_changelist'), reverse("admin:django_q_failure_changelist"),
reverse('admin:django_q_failure_change', args=(f.id,)), reverse("admin:django_q_failure_change", args=(f.id,)),
reverse('admin:django_q_failure_history', args=(f.id,)), reverse("admin:django_q_failure_history", args=(f.id,)),
reverse('admin:django_q_failure_delete', args=(f.id,)), reverse("admin:django_q_failure_delete", args=(f.id,)),
# orm queue # orm queue
reverse('admin:django_q_ormq_changelist'), reverse("admin:django_q_ormq_changelist"),
reverse('admin:django_q_ormq_change', args=(q.id,)), reverse("admin:django_q_ormq_change", args=(q.id,)),
reverse('admin:django_q_ormq_history', args=(q.id,)), reverse("admin:django_q_ormq_history", args=(q.id,)),
reverse('admin:django_q_ormq_delete', args=(q.id,)), reverse("admin:django_q_ormq_delete", args=(q.id,)),
) )
for url in admin_urls: for url in admin_urls:
response = admin_client.get(url) response = admin_client.get(url)
assert response.status_code == 200 assert response.status_code == 200
# resubmit the failure # resubmit the failure
url = reverse('admin:django_q_failure_changelist') url = reverse("admin:django_q_failure_changelist")
data = {'action': 'retry_failed', data = {"action": "retry_failed", "_selected_action": [f.pk]}
'_selected_action': [f.pk]}
response = admin_client.post(url, data) response = admin_client.post(url, data)
assert response.status_code == 302 assert response.status_code == 302
assert Failure.objects.filter(name=f.id).exists() is False assert Failure.objects.filter(name=f.id).exists() is False
# change q # change q
url = reverse('admin:django_q_ormq_change', args=(q.id,)) url = reverse("admin:django_q_ormq_change", args=(q.id,))
data = {'key': 'default', 'payload': 'test', 'lock_0': '2015-09-17', 'lock_1': '14:31:51', '_save': 'Save'} data = {
"key": "default",
"payload": "test",
"lock_0": "2015-09-17",
"lock_1": "14:31:51",
"_save": "Save",
}
response = admin_client.post(url, data) response = admin_client.post(url, data)
assert response.status_code == 302 assert response.status_code == 302
# delete q # delete q
url = reverse('admin:django_q_ormq_delete', args=(q.id,)) url = reverse("admin:django_q_ormq_delete", args=(q.id,))
data = {'post': 'yes'} data = {"post": "yes"}
response = admin_client.post(url, data) response = admin_client.post(url, data)
assert response.status_code == 302 assert response.status_code == 302
+2 -2
View File
@@ -4,7 +4,7 @@ from time import sleep
import pytest import pytest
import redis import redis
from django_q.brokers import get_broker, Broker from django_q.brokers import Broker, get_broker
from django_q.conf import Conf from django_q.conf import Conf
from django_q.humanhash import uuid from django_q.humanhash import uuid
@@ -194,7 +194,7 @@ def canceled_sqs(monkeypatch):
"aws_region": os.getenv("AWS_REGION"), "aws_region": os.getenv("AWS_REGION"),
"aws_access_key_id": os.getenv("AWS_ACCESS_KEY_ID"), "aws_access_key_id": os.getenv("AWS_ACCESS_KEY_ID"),
"aws_secret_access_key": os.getenv("AWS_SECRET_ACCESS_KEY"), "aws_secret_access_key": os.getenv("AWS_SECRET_ACCESS_KEY"),
"receive_message_wait_time_seconds": 20 "receive_message_wait_time_seconds": 20,
}, },
) )
# check broker # check broker
+62 -40
View File
@@ -2,17 +2,30 @@ from multiprocessing import Event, Value
import pytest import pytest
from django_q.cluster import pusher, worker, monitor
from django_q.conf import Conf
from django_q.tasks import async_task, result, fetch, count_group, result_group, fetch_group, delete_group, delete_cached, \
async_iter, Chain, async_chain, Iter, AsyncTask
from django_q.brokers import get_broker from django_q.brokers import get_broker
from django_q.cluster import monitor, pusher, worker
from django_q.conf import Conf
from django_q.queues import Queue from django_q.queues import Queue
from django_q.tasks import (
AsyncTask,
Chain,
Iter,
async_chain,
async_iter,
async_task,
count_group,
delete_cached,
delete_group,
fetch,
fetch_group,
result,
result_group,
)
@pytest.fixture @pytest.fixture
def broker(monkeypatch): def broker(monkeypatch):
monkeypatch.setattr(Conf, 'DJANGO_REDIS', 'default') monkeypatch.setattr(Conf, "DJANGO_REDIS", "default")
return get_broker() return get_broker()
@@ -20,16 +33,16 @@ def broker(monkeypatch):
def test_cached(broker): def test_cached(broker):
broker.purge_queue() broker.purge_queue()
broker.cache.clear() broker.cache.clear()
group = 'cache_test' group = "cache_test"
# queue the tests # queue the tests
task_id = async_task('math.copysign', 1, -1, cached=True, broker=broker) task_id = async_task("math.copysign", 1, -1, cached=True, broker=broker)
async_task('math.copysign', 1, -1, cached=True, broker=broker, group=group) async_task("math.copysign", 1, -1, cached=True, broker=broker, group=group)
async_task('math.copysign', 1, -1, cached=True, broker=broker, group=group) async_task("math.copysign", 1, -1, cached=True, broker=broker, group=group)
async_task('math.copysign', 1, -1, cached=True, broker=broker, group=group) async_task("math.copysign", 1, -1, cached=True, broker=broker, group=group)
async_task('math.copysign', 1, -1, cached=True, broker=broker, group=group) async_task("math.copysign", 1, -1, cached=True, broker=broker, group=group)
async_task('math.copysign', 1, -1, cached=True, broker=broker, group=group) async_task("math.copysign", 1, -1, cached=True, broker=broker, group=group)
async_task('math.popysign', 1, -1, cached=True, broker=broker, group=group) async_task("math.popysign", 1, -1, cached=True, broker=broker, group=group)
iter_id = async_iter('math.floor', [i for i in range(10)], cached=True) iter_id = async_iter("math.floor", [i for i in range(10)], cached=True)
# test wait on cache # test wait on cache
# test wait timeout # test wait timeout
assert result(task_id, wait=10, cached=True) is None assert result(task_id, wait=10, cached=True) is None
@@ -48,11 +61,11 @@ def test_cached(broker):
pusher(task_queue, stop_event, broker=broker) pusher(task_queue, stop_event, broker=broker)
assert broker.queue_size() == 0 assert broker.queue_size() == 0
assert task_queue.qsize() == task_count assert task_queue.qsize() == task_count
task_queue.put('STOP') task_queue.put("STOP")
result_queue = Queue() result_queue = Queue()
worker(task_queue, result_queue, Value('f', -1)) worker(task_queue, result_queue, Value("f", -1))
assert result_queue.qsize() == task_count assert result_queue.qsize() == task_count
result_queue.put('STOP') result_queue.put("STOP")
monitor(result_queue) monitor(result_queue)
assert result_queue.qsize() == 0 assert result_queue.qsize() == 0
# assert results # assert results
@@ -85,10 +98,10 @@ def test_iter(broker):
it = [i for i in range(10)] it = [i for i in range(10)]
it2 = [(1, -1), (2, -1), (3, -4), (5, 6)] it2 = [(1, -1), (2, -1), (3, -4), (5, 6)]
it3 = (1, 2, 3, 4, 5) it3 = (1, 2, 3, 4, 5)
t = async_iter('math.floor', it, sync=True) t = async_iter("math.floor", it, sync=True)
t2 = async_iter('math.copysign', it2, sync=True) t2 = async_iter("math.copysign", it2, sync=True)
t3 = async_iter('math.floor', it3, sync=True) t3 = async_iter("math.floor", it3, sync=True)
t4 = async_iter('math.floor', (1,), sync=True) t4 = async_iter("math.floor", (1,), sync=True)
result_t = result(t) result_t = result(t)
assert result_t is not None assert result_t is not None
task_t = fetch(t) task_t = fetch(t)
@@ -97,7 +110,7 @@ def test_iter(broker):
assert result(t3) is not None assert result(t3) is not None
assert result(t4)[0] == 1 assert result(t4)[0] == 1
# test iter class # test iter class
i = Iter('math.copysign', sync=True, cached=True) i = Iter("math.copysign", sync=True, cached=True)
i.append(1, -1) i.append(1, -1)
i.append(2, -1) i.append(2, -1)
i.append(3, -4) i.append(3, -4)
@@ -118,9 +131,9 @@ def test_chain(broker):
broker.purge_queue() broker.purge_queue()
broker.cache.clear() broker.cache.clear()
task_chain = Chain(sync=True) task_chain = Chain(sync=True)
task_chain.append('math.floor', 1) task_chain.append("math.floor", 1)
task_chain.append('math.copysign', 1, -1) task_chain.append("math.copysign", 1, -1)
task_chain.append('math.floor', 2) task_chain.append("math.floor", 2)
assert task_chain.length() == 3 assert task_chain.length() == 3
assert task_chain.current() is None assert task_chain.current() is None
task_chain.run() task_chain.run()
@@ -130,7 +143,7 @@ def test_chain(broker):
t = task_chain.fetch() t = task_chain.fetch()
assert len(t) == task_chain.length() assert len(t) == task_chain.length()
task_chain.cached = True task_chain.cached = True
task_chain.append('math.floor', 3) task_chain.append("math.floor", 3)
assert task_chain.length() == 4 assert task_chain.length() == 4
task_chain.run() task_chain.run()
r = task_chain.result(wait=1000) r = task_chain.result(wait=1000)
@@ -139,16 +152,20 @@ def test_chain(broker):
t = task_chain.fetch() t = task_chain.fetch()
assert len(t) == task_chain.length() assert len(t) == task_chain.length()
# test single # test single
rid = async_chain(['django_q.tests.tasks.hello', 'django_q.tests.tasks.hello'], sync=True, cached=True) rid = async_chain(
assert result_group(rid, cached=True) == ['hello', 'hello'] ["django_q.tests.tasks.hello", "django_q.tests.tasks.hello"],
sync=True,
cached=True,
)
assert result_group(rid, cached=True) == ["hello", "hello"]
@pytest.mark.django_db @pytest.mark.django_db
def test_asynctask_class(broker, monkeypatch): def test_asynctask_class(broker, monkeypatch):
broker.purge_queue() broker.purge_queue()
broker.cache.clear() broker.cache.clear()
a = AsyncTask('math.copysign') a = AsyncTask("math.copysign")
assert a.func == 'math.copysign' assert a.func == "math.copysign"
a.args = (1, -1) a.args = (1, -1)
assert a.started is False assert a.started is False
a.cached = True a.cached = True
@@ -161,29 +178,34 @@ def test_asynctask_class(broker, monkeypatch):
assert a.result() == -1 assert a.result() == -1
assert a.fetch().result == -1 assert a.fetch().result == -1
# again with kwargs # again with kwargs
a = AsyncTask('math.copysign', 1, -1, cached=True, sync=True, broker=broker) a = AsyncTask("math.copysign", 1, -1, cached=True, sync=True, broker=broker)
a.run() a.run()
assert a.result() == -1 assert a.result() == -1
# with q_options # with q_options
a = AsyncTask('math.copysign', 1, -1, q_options={'cached': True, 'sync': False, 'broker': broker}) a = AsyncTask(
"math.copysign",
1,
-1,
q_options={"cached": True, "sync": False, "broker": broker},
)
assert not a.sync assert not a.sync
a.sync = True a.sync = True
assert a.kwargs['q_options']['sync'] is True assert a.kwargs["q_options"]["sync"] is True
a.run() a.run()
assert a.result() == -1 assert a.result() == -1
a.group = 'async_class_test' a.group = "async_class_test"
assert a.group == 'async_class_test' assert a.group == "async_class_test"
a.save = False a.save = False
assert not a.save assert not a.save
a.hook = 'djq.tests.tasks.hello' a.hook = "djq.tests.tasks.hello"
assert a.hook == 'djq.tests.tasks.hello' assert a.hook == "djq.tests.tasks.hello"
assert a.started is False assert a.started is False
a.run() a.run()
assert a.result_group() == [-1] assert a.result_group() == [-1]
assert a.fetch_group() == [a.fetch()] assert a.fetch_group() == [a.fetch()]
# global overrides # global overrides
monkeypatch.setattr(Conf, 'SYNC', True) monkeypatch.setattr(Conf, "SYNC", True)
monkeypatch.setattr(Conf, 'CACHED', True) monkeypatch.setattr(Conf, "CACHED", True)
a = AsyncTask('math.floor', 1.5) a = AsyncTask("math.floor", 1.5)
a.run() a.run()
assert a.result() == 1 assert a.result() == 1
+242 -162
View File
@@ -1,25 +1,34 @@
import os
import sys import sys
import threading import threading
import uuid as uuidlib
from multiprocessing import Event, Value from multiprocessing import Event, Value
from time import sleep from time import sleep
from django.utils import timezone
import uuid as uuidlib
import os
import pytest import pytest
from django.utils import timezone
myPath = os.path.dirname(os.path.abspath(__file__)) myPath = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, myPath + '/../') sys.path.insert(0, myPath + "/../")
from django_q.cluster import Cluster, Sentinel, pusher, worker, monitor, save_task from django_q.brokers import Broker, get_broker
from django_q.humanhash import DEFAULT_WORDLIST, uuid from django_q.cluster import Cluster, Sentinel, monitor, pusher, save_task, worker
from django_q.tasks import fetch, fetch_group, async_task, result, result_group, count_group, delete_group, queue_size
from django_q.models import Task, Success
from django_q.conf import Conf from django_q.conf import Conf
from django_q.status import Stat from django_q.humanhash import DEFAULT_WORDLIST, uuid
from django_q.brokers import get_broker, Broker from django_q.models import Success, Task
from django_q.tests.tasks import multiply, TaskError
from django_q.queues import Queue from django_q.queues import Queue
from django_q.status import Stat
from django_q.tasks import (
async_task,
count_group,
delete_group,
fetch,
fetch_group,
queue_size,
result,
result_group,
)
from django_q.tests.tasks import TaskError, multiply
class WordClass: class WordClass:
@@ -32,7 +41,7 @@ class WordClass:
@pytest.fixture @pytest.fixture
def broker(monkeypatch): def broker(monkeypatch):
monkeypatch.setattr(Conf, 'DJANGO_REDIS', 'default') monkeypatch.setattr(Conf, "DJANGO_REDIS", "default")
return get_broker() return get_broker()
@@ -42,19 +51,21 @@ def test_redis_connection(broker):
@pytest.mark.django_db @pytest.mark.django_db
def test_sync(broker): def test_sync(broker):
task = async_task('django_q.tests.tasks.count_letters', DEFAULT_WORDLIST, broker=broker, sync=True) task = async_task(
"django_q.tests.tasks.count_letters", DEFAULT_WORDLIST, broker=broker, sync=True
)
assert result(task) == 1506 assert result(task) == 1506
@pytest.mark.django_db @pytest.mark.django_db
def test_sync_raise_exception(broker): def test_sync_raise_exception(broker):
with pytest.raises(TaskError): with pytest.raises(TaskError):
async_task('django_q.tests.tasks.raise_exception', broker=broker, sync=True) async_task("django_q.tests.tasks.raise_exception", broker=broker, sync=True)
@pytest.mark.django_db @pytest.mark.django_db
def test_cluster_initial(broker): def test_cluster_initial(broker):
broker.list_key = 'initial_test:q' broker.list_key = "initial_test:q"
broker.delete_queue() broker.delete_queue()
c = Cluster(broker=broker) c = Cluster(broker=broker)
assert c.sentinel is None assert c.sentinel is None
@@ -80,16 +91,23 @@ def test_sentinel():
stop_event = Event() stop_event = Event()
stop_event.set() stop_event.set()
cluster_id = uuidlib.uuid4() cluster_id = uuidlib.uuid4()
s = Sentinel(stop_event, start_event, cluster_id=cluster_id, broker=get_broker('sentinel_test:q')) s = Sentinel(
stop_event,
start_event,
cluster_id=cluster_id,
broker=get_broker("sentinel_test:q"),
)
assert start_event.is_set() assert start_event.is_set()
assert s.status() == Conf.STOPPED assert s.status() == Conf.STOPPED
@pytest.mark.django_db @pytest.mark.django_db
def test_cluster(broker): def test_cluster(broker):
broker.list_key = 'cluster_test:q' broker.list_key = "cluster_test:q"
broker.delete_queue() broker.delete_queue()
task = async_task('django_q.tests.tasks.count_letters', DEFAULT_WORDLIST, broker=broker) task = async_task(
"django_q.tests.tasks.count_letters", DEFAULT_WORDLIST, broker=broker
)
assert broker.queue_size() == 1 assert broker.queue_size() == 1
task_queue = Queue() task_queue = Queue()
assert task_queue.qsize() == 0 assert task_queue.qsize() == 0
@@ -102,12 +120,12 @@ def test_cluster(broker):
assert task_queue.qsize() == 1 assert task_queue.qsize() == 1
assert queue_size(broker=broker) == 0 assert queue_size(broker=broker) == 0
# Test work # Test work
task_queue.put('STOP') task_queue.put("STOP")
worker(task_queue, result_queue, Value('f', -1)) worker(task_queue, result_queue, Value("f", -1))
assert task_queue.qsize() == 0 assert task_queue.qsize() == 0
assert result_queue.qsize() == 1 assert result_queue.qsize() == 1
# Test monitor # Test monitor
result_queue.put('STOP') result_queue.put("STOP")
monitor(result_queue) monitor(result_queue)
assert result_queue.qsize() == 0 assert result_queue.qsize() == 0
# check result # check result
@@ -117,33 +135,63 @@ def test_cluster(broker):
@pytest.mark.django_db @pytest.mark.django_db
def test_enqueue(broker, admin_user): def test_enqueue(broker, admin_user):
broker.list_key = 'cluster_test:q' broker.list_key = "cluster_test:q"
broker.delete_queue() broker.delete_queue()
a = async_task('django_q.tests.tasks.count_letters', DEFAULT_WORDLIST, hook='django_q.tests.test_cluster.assert_result', a = async_task(
broker=broker) "django_q.tests.tasks.count_letters",
b = async_task('django_q.tests.tasks.count_letters2', WordClass(), hook='django_q.tests.test_cluster.assert_result', DEFAULT_WORDLIST,
broker=broker) hook="django_q.tests.test_cluster.assert_result",
broker=broker,
)
b = async_task(
"django_q.tests.tasks.count_letters2",
WordClass(),
hook="django_q.tests.test_cluster.assert_result",
broker=broker,
)
# unknown argument # unknown argument
c = async_task('django_q.tests.tasks.count_letters', DEFAULT_WORDLIST, 'oneargumentoomany', c = async_task(
hook='django_q.tests.test_cluster.assert_bad_result', broker=broker) "django_q.tests.tasks.count_letters",
DEFAULT_WORDLIST,
"oneargumentoomany",
hook="django_q.tests.test_cluster.assert_bad_result",
broker=broker,
)
# unknown function # unknown function
d = async_task('django_q.tests.tasks.does_not_exist', WordClass(), hook='django_q.tests.test_cluster.assert_bad_result', d = async_task(
broker=broker) "django_q.tests.tasks.does_not_exist",
WordClass(),
hook="django_q.tests.test_cluster.assert_bad_result",
broker=broker,
)
# function without result # function without result
e = async_task('django_q.tests.tasks.countdown', 100000, broker=broker) e = async_task("django_q.tests.tasks.countdown", 100000, broker=broker)
# function as instance # function as instance
f = async_task(multiply, 753, 2, hook=assert_result, broker=broker) f = async_task(multiply, 753, 2, hook=assert_result, broker=broker)
# model as argument # model as argument
g = async_task('django_q.tests.tasks.get_task_name', Task(name='John'), broker=broker) g = async_task(
"django_q.tests.tasks.get_task_name", Task(name="John"), broker=broker
)
# args,kwargs, group and broken hook # args,kwargs, group and broken hook
h = async_task('django_q.tests.tasks.word_multiply', 2, word='django', hook='fail.me', broker=broker) h = async_task(
"django_q.tests.tasks.word_multiply",
2,
word="django",
hook="fail.me",
broker=broker,
)
# args unpickle test # args unpickle test
j = async_task('django_q.tests.tasks.get_user_id', admin_user, broker=broker, group='test_j') j = async_task(
"django_q.tests.tasks.get_user_id", admin_user, broker=broker, group="test_j"
)
# q_options and save opt_out test # q_options and save opt_out test
k = async_task('django_q.tests.tasks.get_user_id', admin_user, k = async_task(
q_options={'broker': broker, 'group': 'test_k', 'save': False, 'timeout': 90}) "django_q.tests.tasks.get_user_id",
admin_user,
q_options={"broker": broker, "group": "test_k", "save": False, "timeout": 90},
)
# test unicode # test unicode
assert Task(name='Amalia').__str__()=='Amalia' assert Task(name="Amalia").__str__() == "Amalia"
# check if everything has a task id # check if everything has a task id
assert isinstance(a, str) assert isinstance(a, str)
assert isinstance(b, str) assert isinstance(b, str)
@@ -166,19 +214,19 @@ def test_enqueue(broker, admin_user):
pusher(task_queue, stop_event, broker=broker) pusher(task_queue, stop_event, broker=broker)
assert broker.queue_size() == 0 assert broker.queue_size() == 0
assert task_queue.qsize() == task_count assert task_queue.qsize() == task_count
task_queue.put('STOP') task_queue.put("STOP")
# test wait timeout # test wait timeout
assert result(j, wait=10) is None assert result(j, wait=10) is None
assert fetch(j, wait=10) is None assert fetch(j, wait=10) is None
assert result_group('test_j', wait=10) is None assert result_group("test_j", wait=10) is None
assert result_group('test_j', count=2, wait=10) is None assert result_group("test_j", count=2, wait=10) is None
assert fetch_group('test_j', wait=10) is None assert fetch_group("test_j", wait=10) is None
assert fetch_group('test_j', count=2, wait=10) is None assert fetch_group("test_j", count=2, wait=10) is None
# let a worker handle them # let a worker handle them
result_queue = Queue() result_queue = Queue()
worker(task_queue, result_queue, Value('f', -1)) worker(task_queue, result_queue, Value("f", -1))
assert result_queue.qsize() == task_count assert result_queue.qsize() == task_count
result_queue.put('STOP') result_queue.put("STOP")
# store the results # store the results
monitor(result_queue) monitor(result_queue)
assert result_queue.qsize() == 0 assert result_queue.qsize() == 0
@@ -215,7 +263,7 @@ def test_enqueue(broker, admin_user):
result_g = fetch(g) result_g = fetch(g)
assert result_g is not None assert result_g is not None
assert result_g.success is True assert result_g.success is True
assert result(g) == 'John' assert result(g) == "John"
# task h # task h
result_h = fetch(h) result_h = fetch(h)
assert result_h is not None assert result_h is not None
@@ -230,19 +278,19 @@ def test_enqueue(broker, admin_user):
assert fetch(result_j.name) == result_j assert fetch(result_j.name) == result_j
assert result(result_j.name) == result_j.result assert result(result_j.name) == result_j.result
# groups # groups
assert result_group('test_j')[0] == result_j.result assert result_group("test_j")[0] == result_j.result
assert result_j.group_result()[0] == result_j.result assert result_j.group_result()[0] == result_j.result
assert result_group('test_j', failures=True)[0] == result_j.result assert result_group("test_j", failures=True)[0] == result_j.result
assert result_j.group_result(failures=True)[0] == result_j.result assert result_j.group_result(failures=True)[0] == result_j.result
assert fetch_group('test_j')[0].id == [result_j][0].id assert fetch_group("test_j")[0].id == [result_j][0].id
assert fetch_group('test_j', failures=False)[0].id == [result_j][0].id assert fetch_group("test_j", failures=False)[0].id == [result_j][0].id
assert count_group('test_j') == 1 assert count_group("test_j") == 1
assert result_j.group_count() == 1 assert result_j.group_count() == 1
assert count_group('test_j', failures=True) == 0 assert count_group("test_j", failures=True) == 0
assert result_j.group_count(failures=True) == 0 assert result_j.group_count(failures=True) == 0
assert delete_group('test_j') == 1 assert delete_group("test_j") == 1
assert result_j.group_delete() == 0 assert result_j.group_delete() == 0
deleted_group = delete_group('test_j', tasks=True) deleted_group = delete_group("test_j", tasks=True)
assert deleted_group is None or deleted_group[0] == 0 # Django 1.9 assert deleted_group is None or deleted_group[0] == 0 # Django 1.9
deleted_group = result_j.group_delete(tasks=True) deleted_group = result_j.group_delete(tasks=True)
assert deleted_group is None or deleted_group[0] == 0 # Django 1.9 assert deleted_group is None or deleted_group[0] == 0 # Django 1.9
@@ -254,22 +302,31 @@ def test_enqueue(broker, admin_user):
@pytest.mark.django_db @pytest.mark.django_db
@pytest.mark.parametrize('cluster_config_timeout, async_task_kwargs', ( @pytest.mark.parametrize(
(1, {}), "cluster_config_timeout, async_task_kwargs",
(10, {'timeout': 1}), (
(None, {'timeout': 1}), (1, {}),
)) (10, {"timeout": 1}),
(None, {"timeout": 1}),
),
)
def test_timeout(broker, cluster_config_timeout, async_task_kwargs): def test_timeout(broker, cluster_config_timeout, async_task_kwargs):
# set up the Sentinel # set up the Sentinel
broker.list_key = 'timeout_test:q' broker.list_key = "timeout_test:q"
broker.purge_queue() broker.purge_queue()
async_task('time.sleep', 5, broker=broker, **async_task_kwargs) async_task("time.sleep", 5, broker=broker, **async_task_kwargs)
start_event = Event() start_event = Event()
stop_event = Event() stop_event = Event()
cluster_id = uuidlib.uuid4() cluster_id = uuidlib.uuid4()
# Set a timer to stop the Sentinel # Set a timer to stop the Sentinel
threading.Timer(3, stop_event.set).start() threading.Timer(3, stop_event.set).start()
s = Sentinel(stop_event, start_event, cluster_id=cluster_id, broker=broker, timeout=cluster_config_timeout) s = Sentinel(
stop_event,
start_event,
cluster_id=cluster_id,
broker=broker,
timeout=cluster_config_timeout,
)
assert start_event.is_set() assert start_event.is_set()
assert s.status() == Conf.STOPPED assert s.status() == Conf.STOPPED
assert s.reincarnations == 1 assert s.reincarnations == 1
@@ -277,23 +334,32 @@ def test_timeout(broker, cluster_config_timeout, async_task_kwargs):
@pytest.mark.django_db @pytest.mark.django_db
@pytest.mark.parametrize('cluster_config_timeout, async_task_kwargs', ( @pytest.mark.parametrize(
(5, {}), "cluster_config_timeout, async_task_kwargs",
(10, {'timeout': 5}), (
(1, {'timeout': 5}), (5, {}),
(None, {'timeout': 5}), (10, {"timeout": 5}),
)) (1, {"timeout": 5}),
(None, {"timeout": 5}),
),
)
def test_timeout_task_finishes(broker, cluster_config_timeout, async_task_kwargs): def test_timeout_task_finishes(broker, cluster_config_timeout, async_task_kwargs):
# set up the Sentinel # set up the Sentinel
broker.list_key = 'timeout_test:q' broker.list_key = "timeout_test:q"
broker.purge_queue() broker.purge_queue()
async_task('time.sleep', 3, broker=broker, **async_task_kwargs) async_task("time.sleep", 3, broker=broker, **async_task_kwargs)
start_event = Event() start_event = Event()
stop_event = Event() stop_event = Event()
cluster_id = uuidlib.uuid4() cluster_id = uuidlib.uuid4()
# Set a timer to stop the Sentinel # Set a timer to stop the Sentinel
threading.Timer(6, stop_event.set).start() threading.Timer(6, stop_event.set).start()
s = Sentinel(stop_event, start_event, cluster_id=cluster_id, broker=broker, timeout=cluster_config_timeout) s = Sentinel(
stop_event,
start_event,
cluster_id=cluster_id,
broker=broker,
timeout=cluster_config_timeout,
)
assert start_event.is_set() assert start_event.is_set()
assert s.status() == Conf.STOPPED assert s.status() == Conf.STOPPED
assert s.reincarnations == 0 assert s.reincarnations == 0
@@ -303,70 +369,71 @@ def test_timeout_task_finishes(broker, cluster_config_timeout, async_task_kwargs
@pytest.mark.django_db @pytest.mark.django_db
def test_recycle(broker, monkeypatch): def test_recycle(broker, monkeypatch):
# set up the Sentinel # set up the Sentinel
broker.list_key = 'test_recycle_test:q' broker.list_key = "test_recycle_test:q"
async_task('django_q.tests.tasks.multiply', 2, 2, broker=broker) async_task("django_q.tests.tasks.multiply", 2, 2, broker=broker)
async_task('django_q.tests.tasks.multiply', 2, 2, broker=broker) async_task("django_q.tests.tasks.multiply", 2, 2, broker=broker)
async_task('django_q.tests.tasks.multiply', 2, 2, broker=broker) async_task("django_q.tests.tasks.multiply", 2, 2, broker=broker)
start_event = Event() start_event = Event()
stop_event = Event() stop_event = Event()
cluster_id = uuidlib.uuid4() cluster_id = uuidlib.uuid4()
# override settings # override settings
monkeypatch.setattr(Conf, 'RECYCLE', 2) monkeypatch.setattr(Conf, "RECYCLE", 2)
monkeypatch.setattr(Conf, 'WORKERS', 1) monkeypatch.setattr(Conf, "WORKERS", 1)
# set a timer to stop the Sentinel # set a timer to stop the Sentinel
threading.Timer(3, stop_event.set).start() threading.Timer(3, stop_event.set).start()
s = Sentinel(stop_event, start_event, cluster_id=cluster_id, broker=broker) s = Sentinel(stop_event, start_event, cluster_id=cluster_id, broker=broker)
assert start_event.is_set() assert start_event.is_set()
assert s.status() == Conf.STOPPED assert s.status() == Conf.STOPPED
assert s.reincarnations == 1 assert s.reincarnations == 1
async_task('django_q.tests.tasks.multiply', 2, 2, broker=broker) async_task("django_q.tests.tasks.multiply", 2, 2, broker=broker)
async_task('django_q.tests.tasks.multiply', 2, 2, broker=broker) async_task("django_q.tests.tasks.multiply", 2, 2, broker=broker)
task_queue = Queue() task_queue = Queue()
result_queue = Queue() result_queue = Queue()
# push two tasks # push two tasks
pusher(task_queue, stop_event, broker=broker) pusher(task_queue, stop_event, broker=broker)
pusher(task_queue, stop_event, broker=broker) pusher(task_queue, stop_event, broker=broker)
# worker should exit on recycle # worker should exit on recycle
worker(task_queue, result_queue, Value('f', -1)) worker(task_queue, result_queue, Value("f", -1))
# check if the work has been done # check if the work has been done
assert result_queue.qsize() == 2 assert result_queue.qsize() == 2
# save_limit test # save_limit test
monkeypatch.setattr(Conf, 'SAVE_LIMIT', 1) monkeypatch.setattr(Conf, "SAVE_LIMIT", 1)
result_queue.put('STOP') result_queue.put("STOP")
# run monitor # run monitor
monitor(result_queue) monitor(result_queue)
assert Success.objects.count() == Conf.SAVE_LIMIT assert Success.objects.count() == Conf.SAVE_LIMIT
broker.delete_queue() broker.delete_queue()
@pytest.mark.django_db @pytest.mark.django_db
def test_max_rss(broker, monkeypatch): def test_max_rss(broker, monkeypatch):
# set up the Sentinel # set up the Sentinel
broker.list_key = 'test_max_rss_test:q' broker.list_key = "test_max_rss_test:q"
async_task('django_q.tests.tasks.multiply', 2, 2, broker=broker) async_task("django_q.tests.tasks.multiply", 2, 2, broker=broker)
start_event = Event() start_event = Event()
stop_event = Event() stop_event = Event()
cluster_id = uuidlib.uuid4() cluster_id = uuidlib.uuid4()
# override settings # override settings
monkeypatch.setattr(Conf, 'MAX_RSS', 40000) monkeypatch.setattr(Conf, "MAX_RSS", 40000)
monkeypatch.setattr(Conf, 'WORKERS', 1) monkeypatch.setattr(Conf, "WORKERS", 1)
# set a timer to stop the Sentinel # set a timer to stop the Sentinel
threading.Timer(3, stop_event.set).start() threading.Timer(3, stop_event.set).start()
s = Sentinel(stop_event, start_event, cluster_id=cluster_id, broker=broker) s = Sentinel(stop_event, start_event, cluster_id=cluster_id, broker=broker)
assert start_event.is_set() assert start_event.is_set()
assert s.status() == Conf.STOPPED assert s.status() == Conf.STOPPED
assert s.reincarnations == 1 assert s.reincarnations == 1
async_task('django_q.tests.tasks.multiply', 2, 2, broker=broker) async_task("django_q.tests.tasks.multiply", 2, 2, broker=broker)
task_queue = Queue() task_queue = Queue()
result_queue = Queue() result_queue = Queue()
# push the task # push the task
pusher(task_queue, stop_event, broker=broker) pusher(task_queue, stop_event, broker=broker)
# worker should exit on recycle # worker should exit on recycle
worker(task_queue, result_queue, Value('f', -1)) worker(task_queue, result_queue, Value("f", -1))
# check if the work has been done # check if the work has been done
assert result_queue.qsize() == 1 assert result_queue.qsize() == 1
# save_limit test # save_limit test
monkeypatch.setattr(Conf, 'SAVE_LIMIT', 1) monkeypatch.setattr(Conf, "SAVE_LIMIT", 1)
result_queue.put('STOP') result_queue.put("STOP")
# run monitor # run monitor
monitor(result_queue) monitor(result_queue)
assert Success.objects.count() == Conf.SAVE_LIMIT assert Success.objects.count() == Conf.SAVE_LIMIT
@@ -375,13 +442,15 @@ def test_max_rss(broker, monkeypatch):
@pytest.mark.django_db @pytest.mark.django_db
def test_bad_secret(broker, monkeypatch): def test_bad_secret(broker, monkeypatch):
broker.list_key = 'test_bad_secret:q' broker.list_key = "test_bad_secret:q"
async_task('math.copysign', 1, -1, broker=broker) async_task("math.copysign", 1, -1, broker=broker)
stop_event = Event() stop_event = Event()
stop_event.set() stop_event.set()
start_event = Event() start_event = Event()
cluster_id = uuidlib.uuid4() cluster_id = uuidlib.uuid4()
s = Sentinel(stop_event, start_event, cluster_id=cluster_id, broker=broker, start=False) s = Sentinel(
stop_event, start_event, cluster_id=cluster_id, broker=broker, start=False
)
Stat(s).save() Stat(s).save()
# change the SECRET # change the SECRET
monkeypatch.setattr(Conf, "SECRET_KEY", "OOPS") monkeypatch.setattr(Conf, "SECRET_KEY", "OOPS")
@@ -391,87 +460,94 @@ def test_bad_secret(broker, monkeypatch):
task_queue = Queue() task_queue = Queue()
pusher(task_queue, stop_event, broker=broker) pusher(task_queue, stop_event, broker=broker)
result_queue = Queue() result_queue = Queue()
task_queue.put('STOP') task_queue.put("STOP")
worker(task_queue, result_queue, Value('f', -1), ) worker(
task_queue,
result_queue,
Value("f", -1),
)
assert result_queue.qsize() == 0 assert result_queue.qsize() == 0
broker.delete_queue() broker.delete_queue()
@pytest.mark.django_db @pytest.mark.django_db
def test_attempt_count(broker, monkeypatch): def test_attempt_count(broker, monkeypatch):
monkeypatch.setattr(Conf, 'MAX_ATTEMPTS', 3) monkeypatch.setattr(Conf, "MAX_ATTEMPTS", 3)
tag = uuid() tag = uuid()
task = {'id': tag[1], task = {
'name': tag[0], "id": tag[1],
'func': 'math.copysign', "name": tag[0],
'args': (1, -1), "func": "math.copysign",
'kwargs': {}, "args": (1, -1),
'started': timezone.now(), "kwargs": {},
'stopped': timezone.now(), "started": timezone.now(),
'success': False, "stopped": timezone.now(),
'result': None} "success": False,
"result": None,
}
# initial save - no success # initial save - no success
save_task(task, broker) save_task(task, broker)
assert Task.objects.filter(id=task['id']).exists() assert Task.objects.filter(id=task["id"]).exists()
saved_task = Task.objects.get(id=task['id']) saved_task = Task.objects.get(id=task["id"])
assert saved_task.attempt_count == 1 assert saved_task.attempt_count == 1
sleep(0.5) sleep(0.5)
# second save # second save
old_stopped = task['stopped'] old_stopped = task["stopped"]
task['stopped'] = timezone.now() task["stopped"] = timezone.now()
save_task(task, broker) save_task(task, broker)
saved_task = Task.objects.get(id=task['id']) saved_task = Task.objects.get(id=task["id"])
assert saved_task.attempt_count == 2 assert saved_task.attempt_count == 2
# third save - # third save -
task['stopped'] = timezone.now() task["stopped"] = timezone.now()
save_task(task, broker) save_task(task, broker)
saved_task = Task.objects.get(id=task['id']) saved_task = Task.objects.get(id=task["id"])
assert saved_task.attempt_count == 3 assert saved_task.attempt_count == 3
# task should be removed from queue # task should be removed from queue
assert broker.queue_size() == 0 assert broker.queue_size() == 0
@pytest.mark.django_db @pytest.mark.django_db
def test_update_failed(broker): def test_update_failed(broker):
tag = uuid() tag = uuid()
task = {'id': tag[1], task = {
'name': tag[0], "id": tag[1],
'func': 'math.copysign', "name": tag[0],
'args': (1, -1), "func": "math.copysign",
'kwargs': {}, "args": (1, -1),
'started': timezone.now(), "kwargs": {},
'stopped': timezone.now(), "started": timezone.now(),
'success': False, "stopped": timezone.now(),
'result': None} "success": False,
"result": None,
}
# initial save - no success # initial save - no success
save_task(task, broker) save_task(task, broker)
assert Task.objects.filter(id=task['id']).exists() assert Task.objects.filter(id=task["id"]).exists()
saved_task = Task.objects.get(id=task['id']) saved_task = Task.objects.get(id=task["id"])
assert saved_task.success is False assert saved_task.success is False
sleep(0.5) sleep(0.5)
# second save - no success # second save - no success
old_stopped = task['stopped'] old_stopped = task["stopped"]
task['stopped'] = timezone.now() task["stopped"] = timezone.now()
save_task(task, broker) save_task(task, broker)
saved_task = Task.objects.get(id=task['id']) saved_task = Task.objects.get(id=task["id"])
assert saved_task.stopped > old_stopped assert saved_task.stopped > old_stopped
# third save - success # third save - success
task['stopped'] = timezone.now() task["stopped"] = timezone.now()
task['result'] = 'result' task["result"] = "result"
task['success'] = True task["success"] = True
save_task(task, broker) save_task(task, broker)
saved_task = Task.objects.get(id=task['id']) saved_task = Task.objects.get(id=task["id"])
assert saved_task.success is True assert saved_task.success is True
# fourth save - no success # fourth save - no success
task['result'] = None task["result"] = None
task['success'] = False task["success"] = False
task['stopped'] = old_stopped task["stopped"] = old_stopped
save_task(task, broker) save_task(task, broker)
# should not overwrite success # should not overwrite success
saved_task = Task.objects.get(id=task['id']) saved_task = Task.objects.get(id=task["id"])
assert saved_task.success is True assert saved_task.success is True
assert saved_task.result == 'result' assert saved_task.result == "result"
@pytest.mark.django_db @pytest.mark.django_db
@@ -486,47 +562,51 @@ def test_acknowledge_failure_override():
self.acknowledgements[task_id] = count + 1 self.acknowledgements[task_id] = count + 1
tag = uuid() tag = uuid()
task_fail_ack = {'id': tag[1], task_fail_ack = {
'name': tag[0], "id": tag[1],
'ack_id': 'test_fail_ack_id', "name": tag[0],
'ack_failure': True, "ack_id": "test_fail_ack_id",
'func': 'math.copysign', "ack_failure": True,
'args': (1, -1), "func": "math.copysign",
'kwargs': {}, "args": (1, -1),
'started': timezone.now(), "kwargs": {},
'stopped': timezone.now(), "started": timezone.now(),
'success': False, "stopped": timezone.now(),
'result': None} "success": False,
"result": None,
}
tag = uuid() tag = uuid()
task_fail_no_ack = task_fail_ack.copy() task_fail_no_ack = task_fail_ack.copy()
task_fail_no_ack.update({'id': tag[1], task_fail_no_ack.update(
'name': tag[0], {"id": tag[1], "name": tag[0], "ack_id": "test_fail_no_ack_id"}
'ack_id': 'test_fail_no_ack_id'}) )
del task_fail_no_ack['ack_failure'] del task_fail_no_ack["ack_failure"]
tag = uuid() tag = uuid()
task_success_ack = task_fail_ack.copy() task_success_ack = task_fail_ack.copy()
task_success_ack.update({ task_success_ack.update(
'id': tag[1], {
'name': tag[0], "id": tag[1],
'ack_id': 'test_success_ack_id', "name": tag[0],
'success': True, "ack_id": "test_success_ack_id",
}) "success": True,
del task_success_ack['ack_failure'] }
)
del task_success_ack["ack_failure"]
result_queue = Queue() result_queue = Queue()
result_queue.put(task_fail_ack) result_queue.put(task_fail_ack)
result_queue.put(task_fail_no_ack) result_queue.put(task_fail_no_ack)
result_queue.put(task_success_ack) result_queue.put(task_success_ack)
result_queue.put('STOP') result_queue.put("STOP")
broker = VerifyAckMockBroker(list_key='key') broker = VerifyAckMockBroker(list_key="key")
monitor(result_queue, broker) monitor(result_queue, broker)
assert broker.acknowledgements.get('test_fail_ack_id') == 1 assert broker.acknowledgements.get("test_fail_ack_id") == 1
assert broker.acknowledgements.get('test_fail_no_ack_id') is None assert broker.acknowledgements.get("test_fail_no_ack_id") is None
assert broker.acknowledgements.get('test_success_ack_id') == 1 assert broker.acknowledgements.get("test_success_ack_id") == 1
@pytest.mark.django_db @pytest.mark.django_db
+7 -7
View File
@@ -4,22 +4,22 @@ from django.core.management import call_command
@pytest.mark.django_db @pytest.mark.django_db
def test_qcluster(): def test_qcluster():
call_command('qcluster', run_once=True) call_command("qcluster", run_once=True)
@pytest.mark.django_db @pytest.mark.django_db
def test_qmonitor(): def test_qmonitor():
call_command('qmonitor', run_once=True) call_command("qmonitor", run_once=True)
@pytest.mark.django_db @pytest.mark.django_db
def test_qinfo(): def test_qinfo():
call_command('qinfo') call_command("qinfo")
call_command('qinfo', config=True) call_command("qinfo", config=True)
call_command('qinfo', ids=True) call_command("qinfo", ids=True)
@pytest.mark.django_db @pytest.mark.django_db
def test_qmemory(): def test_qmemory():
call_command('qmemory', run_once=True) call_command("qmemory", run_once=True)
call_command('qmemory', workers=True, run_once=True) call_command("qmemory", workers=True, run_once=True)
+9 -8
View File
@@ -1,12 +1,13 @@
import pytest
import uuid import uuid
from django_q.tasks import async_task import pytest
from django_q.brokers import get_broker from django_q.brokers import get_broker
from django_q.cluster import Cluster from django_q.cluster import Cluster
from django_q.monitor import monitor, info, get_ids
from django_q.status import Stat
from django_q.conf import Conf from django_q.conf import Conf
from django_q.monitor import get_ids, info, monitor
from django_q.status import Stat
from django_q.tasks import async_task
@pytest.mark.django_db @pytest.mark.django_db
@@ -28,9 +29,9 @@ def test_monitor(monkeypatch):
break break
assert found_c assert found_c
# test lock size # test lock size
monkeypatch.setattr(Conf, 'ORM', 'default') monkeypatch.setattr(Conf, "ORM", "default")
b = get_broker('monitor_test') b = get_broker("monitor_test")
b.enqueue('test') b.enqueue("test")
b.dequeue() b.dequeue()
assert b.lock_size() == 1 assert b.lock_size() == 1
monitor(run_once=True, broker=b) monitor(run_once=True, broker=b)
@@ -48,4 +49,4 @@ def test_info():
def do_sync(): def do_sync():
async_task('django_q.tests.tasks.countdown', 1, sync=True, save=True) async_task("django_q.tests.tasks.countdown", 1, sync=True, save=True)
+162 -122
View File
@@ -9,89 +9,102 @@ from django.core.exceptions import ValidationError
from django.db import IntegrityError from django.db import IntegrityError
from django.test import override_settings from django.test import override_settings
from django.utils import timezone from django.utils import timezone
from django.utils.timezone import is_naive
from django_q.brokers import get_broker, Broker from django_q.brokers import Broker, get_broker
from django_q.cluster import pusher, worker, monitor, scheduler from django_q.cluster import monitor, pusher, scheduler, worker, localtime
from django_q.conf import Conf from django_q.conf import Conf
from django_q.queues import Queue from django_q.queues import Queue
from django_q.tasks import Schedule, fetch, schedule as create_schedule from django_q.tasks import Schedule, fetch
from django_q.tasks import schedule as create_schedule
from django_q.tests.settings import BASE_DIR from django_q.tests.settings import BASE_DIR
from django_q.tests.testing_utilities.multiple_database_routers import (TestingReplicaDatabaseRouter, from django_q.tests.testing_utilities.multiple_database_routers import (
TestingMultipleAppsDatabaseRouter) TestingMultipleAppsDatabaseRouter,
TestingReplicaDatabaseRouter,
)
@pytest.fixture @pytest.fixture
def broker(monkeypatch) -> Broker: def broker(monkeypatch) -> Broker:
"""Patches the Conf object setting the DJANGO_REDIS attribute allowing a default redis configuration.""" """Patches the Conf object setting the DJANGO_REDIS attribute allowing a default redis configuration."""
monkeypatch.setattr(Conf, 'DJANGO_REDIS', 'default') monkeypatch.setattr(Conf, "DJANGO_REDIS", "default")
return get_broker() return get_broker()
@pytest.fixture @pytest.fixture
def orm_broker(monkeypatch) -> None: def orm_broker(monkeypatch) -> None:
"""Patches the Conf object setting the ORM attribute to a database named default.""" """Patches the Conf object setting the ORM attribute to a database named default."""
monkeypatch.setattr(Conf, 'ORM', 'default') monkeypatch.setattr(Conf, "ORM", "default")
@pytest.fixture @pytest.fixture
def orm_no_replica_broker(orm_broker, monkeypatch) -> Broker: def orm_no_replica_broker(orm_broker, monkeypatch) -> Broker:
"""Generates a Broker with a disabled read replica database configuration.""" """Generates a Broker with a disabled read replica database configuration."""
monkeypatch.setattr(Conf, 'HAS_REPLICA', False) monkeypatch.setattr(Conf, "HAS_REPLICA", False)
return get_broker(list_key='scheduler_test:q') return get_broker(list_key="scheduler_test:q")
@pytest.fixture @pytest.fixture
def orm_replica_broker(orm_broker, monkeypatch) -> Broker: def orm_replica_broker(orm_broker, monkeypatch) -> Broker:
"""Generates a Broker with read replica database configuration.""" """Generates a Broker with read replica database configuration."""
monkeypatch.setattr(Conf, 'HAS_REPLICA', True) monkeypatch.setattr(Conf, "HAS_REPLICA", True)
return get_broker(list_key='scheduler_test:q') return get_broker(list_key="scheduler_test:q")
REPLICA_DATABASE_ROUTERS = [f"{TestingReplicaDatabaseRouter.__module__}.{TestingReplicaDatabaseRouter.__name__}"] REPLICA_DATABASE_ROUTERS = [
f"{TestingReplicaDatabaseRouter.__module__}.{TestingReplicaDatabaseRouter.__name__}"
]
REPLICA_DATABASES = { REPLICA_DATABASES = {
'default': { "default": {
'ENGINE': 'django.db.backends.sqlite3', "ENGINE": "django.db.backends.sqlite3",
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), "NAME": os.path.join(BASE_DIR, "db.sqlite3"),
}, },
'replica': { "replica": {
'ENGINE': 'django.db.backends.sqlite3', "ENGINE": "django.db.backends.sqlite3",
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), "NAME": os.path.join(BASE_DIR, "db.sqlite3"),
}, },
} }
MULTIPLE_APPS_DATABASE_ROUTERS = [ MULTIPLE_APPS_DATABASE_ROUTERS = [
f"{TestingMultipleAppsDatabaseRouter.__module__}.{TestingMultipleAppsDatabaseRouter.__name__}"] f"{TestingMultipleAppsDatabaseRouter.__module__}.{TestingMultipleAppsDatabaseRouter.__name__}"
]
MULTIPLE_APPS_DATABASES = { MULTIPLE_APPS_DATABASES = {
'default': { "default": {
'ENGINE': 'django.db.backends.sqlite3', "ENGINE": "django.db.backends.sqlite3",
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), "NAME": os.path.join(BASE_DIR, "db.sqlite3"),
}, },
'admin': { "admin": {
'ENGINE': 'django.db.backends.sqlite3', "ENGINE": "django.db.backends.sqlite3",
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), "NAME": os.path.join(BASE_DIR, "db.sqlite3"),
}, },
} }
@pytest.mark.django_db @pytest.mark.django_db
def test_scheduler(broker, monkeypatch): def test_scheduler(broker, monkeypatch):
broker.list_key = 'scheduler_test:q' broker.list_key = "scheduler_test:q"
broker.delete_queue() broker.delete_queue()
schedule = create_schedule('math.copysign', schedule = create_schedule(
1, -1, "math.copysign",
name='test math', 1,
hook='django_q.tests.tasks.result', -1,
schedule_type=Schedule.HOURLY, name="test math",
repeats=1) hook="django_q.tests.tasks.result",
schedule_type=Schedule.HOURLY,
repeats=1,
)
assert schedule.last_run() is None assert schedule.last_run() is None
# check duplicate constraint # check duplicate constraint
with pytest.raises(IntegrityError): with pytest.raises(IntegrityError):
schedule = create_schedule('math.copysign', schedule = create_schedule(
1, -1, "math.copysign",
name='test math', 1,
hook='django_q.tests.tasks.result', -1,
schedule_type=Schedule.HOURLY, name="test math",
repeats=1) hook="django_q.tests.tasks.result",
schedule_type=Schedule.HOURLY,
repeats=1,
)
# run scheduler # run scheduler
scheduler(broker=broker) scheduler(broker=broker)
# set up the workflow # set up the workflow
@@ -102,12 +115,12 @@ def test_scheduler(broker, monkeypatch):
pusher(task_queue, stop_event, broker=broker) pusher(task_queue, stop_event, broker=broker)
assert task_queue.qsize() == 1 assert task_queue.qsize() == 1
assert broker.queue_size() == 0 assert broker.queue_size() == 0
task_queue.put('STOP') task_queue.put("STOP")
# let a worker handle them # let a worker handle them
result_queue = Queue() result_queue = Queue()
worker(task_queue, result_queue, Value('b', -1)) worker(task_queue, result_queue, Value("b", -1))
assert result_queue.qsize() == 1 assert result_queue.qsize() == 1
result_queue.put('STOP') result_queue.put("STOP")
# store the results # store the results
monitor(result_queue) monitor(result_queue)
assert result_queue.qsize() == 0 assert result_queue.qsize() == 0
@@ -121,99 +134,113 @@ def test_scheduler(broker, monkeypatch):
assert task.success is True assert task.success is True
assert task.result < 0 assert task.result < 0
# Once schedule with delete # Once schedule with delete
once_schedule = create_schedule('django_q.tests.tasks.word_multiply', once_schedule = create_schedule(
2, "django_q.tests.tasks.word_multiply",
word='django', 2,
schedule_type=Schedule.ONCE, word="django",
repeats=-1, schedule_type=Schedule.ONCE,
hook='django_q.tests.tasks.result' repeats=-1,
) hook="django_q.tests.tasks.result",
assert hasattr(once_schedule, 'pk') is True )
assert hasattr(once_schedule, "pk") is True
# negative repeats # negative repeats
always_schedule = create_schedule('django_q.tests.tasks.word_multiply', always_schedule = create_schedule(
2, "django_q.tests.tasks.word_multiply",
word='django', 2,
schedule_type=Schedule.DAILY, word="django",
repeats=-1, schedule_type=Schedule.DAILY,
hook='django_q.tests.tasks.result' repeats=-1,
) hook="django_q.tests.tasks.result",
assert hasattr(always_schedule, 'pk') is True )
assert hasattr(always_schedule, "pk") is True
# Minute schedule # Minute schedule
minute_schedule = create_schedule('django_q.tests.tasks.word_multiply', minute_schedule = create_schedule(
2, "django_q.tests.tasks.word_multiply",
word='django', 2,
schedule_type=Schedule.MINUTES, word="django",
minutes=10) schedule_type=Schedule.MINUTES,
assert hasattr(minute_schedule, 'pk') is True minutes=10,
)
assert hasattr(minute_schedule, "pk") is True
# Cron schedule # Cron schedule
cron_schedule = create_schedule('django_q.tests.tasks.word_multiply', cron_schedule = create_schedule(
2, "django_q.tests.tasks.word_multiply",
word='django', 2,
schedule_type=Schedule.CRON, word="django",
cron="0 22 * * 1-5") schedule_type=Schedule.CRON,
assert hasattr(cron_schedule, 'pk') is True cron="0 22 * * 1-5",
)
assert hasattr(cron_schedule, "pk") is True
assert cron_schedule.full_clean() is None assert cron_schedule.full_clean() is None
assert cron_schedule.__str__() == 'django_q.tests.tasks.word_multiply' assert cron_schedule.__str__() == "django_q.tests.tasks.word_multiply"
with pytest.raises(ValidationError): with pytest.raises(ValidationError):
create_schedule('django_q.tests.tasks.word_multiply', create_schedule(
2, "django_q.tests.tasks.word_multiply",
word='django', 2,
schedule_type=Schedule.CRON, word="django",
cron="0 22 * * 1-12") schedule_type=Schedule.CRON,
cron="0 22 * * 1-12",
)
# All other types # All other types
for t in Schedule.TYPE: for t in Schedule.TYPE:
if t[0] == Schedule.CRON: if t[0] == Schedule.CRON:
continue continue
schedule = create_schedule('django_q.tests.tasks.word_multiply', schedule = create_schedule(
2, "django_q.tests.tasks.word_multiply",
word='django', 2,
schedule_type=t[0], word="django",
repeats=1, schedule_type=t[0],
hook='django_q.tests.tasks.result' repeats=1,
) hook="django_q.tests.tasks.result",
)
assert schedule is not None assert schedule is not None
assert schedule.last_run() is None assert schedule.last_run() is None
scheduler(broker=broker) scheduler(broker=broker)
# via model # via model
Schedule.objects.create(func='django_q.tests.tasks.word_multiply', Schedule.objects.create(
args='2', func="django_q.tests.tasks.word_multiply",
kwargs='word="django"', args="2",
schedule_type=Schedule.DAILY kwargs='word="django"',
) schedule_type=Schedule.DAILY,
)
# scheduler # scheduler
scheduler(broker=broker) scheduler(broker=broker)
# ONCE schedule should be deleted # ONCE schedule should be deleted
assert Schedule.objects.filter(pk=once_schedule.pk).exists() is False assert Schedule.objects.filter(pk=once_schedule.pk).exists() is False
# Catch up On # Catch up On
monkeypatch.setattr(Conf, 'CATCH_UP', True) monkeypatch.setattr(Conf, "CATCH_UP", True)
now = timezone.now() now = timezone.now()
schedule = create_schedule('django_q.tests.tasks.word_multiply', schedule = create_schedule(
2, "django_q.tests.tasks.word_multiply",
word='catch_up', 2,
schedule_type=Schedule.HOURLY, word="catch_up",
next_run=timezone.now() - timedelta(hours=12), schedule_type=Schedule.HOURLY,
repeats=-1 next_run=timezone.now() - timedelta(hours=12),
) repeats=-1,
)
scheduler(broker=broker) scheduler(broker=broker)
schedule = Schedule.objects.get(pk=schedule.pk) schedule = Schedule.objects.get(pk=schedule.pk)
assert schedule.next_run < now assert schedule.next_run < now
# Catch up off # Catch up off
monkeypatch.setattr(Conf, 'CATCH_UP', False) monkeypatch.setattr(Conf, "CATCH_UP", False)
scheduler(broker=broker) scheduler(broker=broker)
schedule = Schedule.objects.get(pk=schedule.pk) schedule = Schedule.objects.get(pk=schedule.pk)
assert schedule.next_run > now assert schedule.next_run > now
# Done # Done
broker.delete_queue() broker.delete_queue()
monkeypatch.setattr(Conf, 'PREFIX', 'some_cluster_name') monkeypatch.setattr(Conf, "PREFIX", "some_cluster_name")
# create a schedule on another cluster # create a schedule on another cluster
schedule = create_schedule('math.copysign', schedule = create_schedule(
1, -1, "math.copysign",
name='test schedule on a another cluster', 1,
hook='django_q.tests.tasks.result', -1,
schedule_type=Schedule.HOURLY, name="test schedule on a another cluster",
cluster="some_other_cluster_name", hook="django_q.tests.tasks.result",
repeats=1) schedule_type=Schedule.HOURLY,
cluster="some_other_cluster_name",
repeats=1,
)
# run scheduler # run scheduler
scheduler(broker=broker) scheduler(broker=broker)
# set up the workflow # set up the workflow
@@ -226,15 +253,18 @@ def test_scheduler(broker, monkeypatch):
# queue must be empty # queue must be empty
assert task_queue.qsize() == 0 assert task_queue.qsize() == 0
monkeypatch.setattr(Conf, 'PREFIX', 'default') monkeypatch.setattr(Conf, "PREFIX", "default")
# create a schedule on the same cluster # create a schedule on the same cluster
schedule = create_schedule('math.copysign', schedule = create_schedule(
1, -1, "math.copysign",
name='test schedule with no cluster', 1,
hook='django_q.tests.tasks.result', -1,
schedule_type=Schedule.HOURLY, name="test schedule with no cluster",
cluster="default", hook="django_q.tests.tasks.result",
repeats=1) schedule_type=Schedule.HOURLY,
cluster="default",
repeats=1,
)
# run scheduler # run scheduler
scheduler(broker=broker) scheduler(broker=broker)
# set up the workflow # set up the workflow
@@ -249,11 +279,12 @@ def test_scheduler(broker, monkeypatch):
@override_settings( @override_settings(
DATABASE_ROUTERS=REPLICA_DATABASE_ROUTERS, DATABASE_ROUTERS=REPLICA_DATABASE_ROUTERS, DATABASES=REPLICA_DATABASES
DATABASES=REPLICA_DATABASES
) )
@pytest.mark.django_db @pytest.mark.django_db
def test_scheduler_atomic_transaction_must_specify_a_database_when_no_replicas_are_used(orm_no_replica_broker: Broker): def test_scheduler_atomic_transaction_must_specify_a_database_when_no_replicas_are_used(
orm_no_replica_broker: Broker,
):
""" """
GIVEN a environment without a read replica database GIVEN a environment without a read replica database
WHEN the scheduler is called WHEN the scheduler is called
@@ -267,12 +298,12 @@ def test_scheduler_atomic_transaction_must_specify_a_database_when_no_replicas_a
@override_settings( @override_settings(
DATABASE_ROUTERS=REPLICA_DATABASE_ROUTERS, DATABASE_ROUTERS=REPLICA_DATABASE_ROUTERS, DATABASES=REPLICA_DATABASES
DATABASES=REPLICA_DATABASES
) )
@pytest.mark.django_db @pytest.mark.django_db
def test_scheduler_atomic_transaction_must_specify_no_database_when_read_write_replicas_are_used( def test_scheduler_atomic_transaction_must_specify_no_database_when_read_write_replicas_are_used(
orm_replica_broker: Broker): orm_replica_broker: Broker,
):
""" """
GIVEN a environment with a read/write configured replica database GIVEN a environment with a read/write configured replica database
WHEN the scheduler is called WHEN the scheduler is called
@@ -285,12 +316,12 @@ def test_scheduler_atomic_transaction_must_specify_no_database_when_read_write_r
@override_settings( @override_settings(
DATABASE_ROUTERS=MULTIPLE_APPS_DATABASE_ROUTERS, DATABASE_ROUTERS=MULTIPLE_APPS_DATABASE_ROUTERS, DATABASES=MULTIPLE_APPS_DATABASES
DATABASES=MULTIPLE_APPS_DATABASES
) )
@pytest.mark.django_db @pytest.mark.django_db
def test_scheduler_atomic_transaction_must_specify_the_database_based_on_router_redirection( def test_scheduler_atomic_transaction_must_specify_the_database_based_on_router_redirection(
orm_no_replica_broker: Broker): orm_no_replica_broker: Broker,
):
""" """
GIVEN a environment without a read replica database GIVEN a environment without a read replica database
WHEN the scheduler is called WHEN the scheduler is called
@@ -300,5 +331,14 @@ def test_scheduler_atomic_transaction_must_specify_the_database_based_on_router_
with mock.patch("django_q.cluster.db") as mocked_db: with mock.patch("django_q.cluster.db") as mocked_db:
scheduler(broker=broker) scheduler(broker=broker)
# The router should correctly set the database to use! # The router should correctly set the database to use!
assert broker.connection.db == 'default' assert broker.connection.db == "default"
mocked_db.transaction.atomic.assert_called_with(using=broker.connection.db) mocked_db.transaction.atomic.assert_called_with(using=broker.connection.db)
def test_localtime():
assert not is_naive(localtime())
@override_settings(USE_TZ=False)
def test_naive_localtime():
assert is_naive(localtime())
@@ -25,14 +25,14 @@ class TestingMultipleAppsDatabaseRouter:
@staticmethod @staticmethod
def is_admin(model): def is_admin(model):
return model._meta.app_label in ['admin'] return model._meta.app_label in ["admin"]
def db_for_read(self, model, **hints): def db_for_read(self, model, **hints):
if self.is_admin(model): if self.is_admin(model):
return 'admin' return "admin"
return 'default' return "default"
def db_for_write(self, model, **hints): def db_for_write(self, model, **hints):
if self.is_admin(model): if self.is_admin(model):
return 'admin' return "admin"
return 'default' return "default"
+2 -2
View File
@@ -1,6 +1,6 @@
from django.urls import re_path
from django.contrib import admin from django.contrib import admin
from django.urls import re_path
urlpatterns = [ urlpatterns = [
re_path(r'^admin/', admin.site.urls), re_path(r"^admin/", admin.site.urls),
] ]
+2 -2
View File
@@ -13,8 +13,8 @@
# All configuration values have a default; values that are commented out # All configuration values have a default; values that are commented out
# serve to show the default. # serve to show the default.
import sys
import os import os
import sys
import alabaster import alabaster
@@ -73,7 +73,7 @@ author = 'Ilan Steemers'
# The short X.Y version. # The short X.Y version.
version = '1.3' version = '1.3'
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = '1.3.6' release = '1.3.7'
# 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.
Generated
+261 -3
View File
@@ -14,6 +14,14 @@ category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
[[package]]
name = "appdirs"
version = "1.4.4"
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "dev"
optional = false
python-versions = "*"
[[package]] [[package]]
name = "arrow" name = "arrow"
version = "0.15.8" version = "0.15.8"
@@ -72,6 +80,30 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[package.dependencies] [package.dependencies]
pytz = ">=2015.7" pytz = ">=2015.7"
[[package]]
name = "black"
version = "21.5b1"
description = "The uncompromising code formatter."
category = "dev"
optional = false
python-versions = ">=3.6.2"
[package.dependencies]
appdirs = "*"
click = ">=7.1.2"
dataclasses = {version = ">=0.6", markers = "python_version < \"3.7\""}
mypy-extensions = ">=0.4.3"
pathspec = ">=0.8.1,<1"
regex = ">=2020.1.8"
toml = ">=0.10.1"
typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\""}
typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""}
[package.extras]
colorama = ["colorama (>=0.4.3)"]
d = ["aiohttp (>=3.6.0)", "aiohttp-cors"]
python2 = ["typed-ast (>=1.4.2)"]
[[package]] [[package]]
name = "blessed" name = "blessed"
version = "1.18.0" version = "1.18.0"
@@ -130,6 +162,18 @@ category = "main"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "click"
version = "8.0.1"
description = "Composable command line interface toolkit"
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
[[package]] [[package]]
name = "colorama" name = "colorama"
version = "0.4.4" version = "0.4.4"
@@ -164,6 +208,14 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
natsort = "*" natsort = "*"
python-dateutil = "*" python-dateutil = "*"
[[package]]
name = "dataclasses"
version = "0.8"
description = "A backport of the dataclasses module for Python 3.6"
category = "dev"
optional = false
python-versions = ">=3.6, <3.7"
[[package]] [[package]]
name = "django" name = "django"
version = "3.2.3" version = "3.2.3"
@@ -229,6 +281,14 @@ python-versions = ">=3.5"
Django = ">=2.2" Django = ">=2.2"
redis = ">=3.0.0" redis = ">=3.0.0"
[[package]]
name = "docopt"
version = "0.6.2"
description = "Pythonic argument parser, that will make you smile"
category = "dev"
optional = false
python-versions = "*"
[[package]] [[package]]
name = "docutils" name = "docutils"
version = "0.17.1" version = "0.17.1"
@@ -300,6 +360,23 @@ python-versions = "*"
[package.dependencies] [package.dependencies]
iron_core = "*" iron_core = "*"
[[package]]
name = "isort"
version = "5.8.0"
description = "A Python utility / library to sort Python imports."
category = "dev"
optional = false
python-versions = ">=3.6,<4.0"
[package.dependencies]
pip-api = {version = "*", optional = true, markers = "extra == \"requirements_deprecated_finder\""}
pipreqs = {version = "*", optional = true, markers = "extra == \"pipfile_deprecated_finder\" or extra == \"requirements_deprecated_finder\""}
[package.extras]
pipfile_deprecated_finder = ["pipreqs", "requirementslib"]
requirements_deprecated_finder = ["pipreqs", "pip-api"]
colors = ["colorama (>=0.4.3,<0.5.0)"]
[[package]] [[package]]
name = "jinja2" name = "jinja2"
version = "3.0.1" version = "3.0.1"
@@ -349,6 +426,14 @@ category = "dev"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.5"
[[package]]
name = "mypy-extensions"
version = "0.4.3"
description = "Experimental type system extensions for programs checked with the mypy typechecker."
category = "dev"
optional = false
python-versions = "*"
[[package]] [[package]]
name = "natsort" name = "natsort"
version = "7.1.1" version = "7.1.1"
@@ -372,6 +457,34 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[package.dependencies] [package.dependencies]
pyparsing = ">=2.0.2" pyparsing = ">=2.0.2"
[[package]]
name = "pathspec"
version = "0.8.1"
description = "Utility library for gitignore style pattern matching of file paths."
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "pip-api"
version = "0.0.20"
description = "An unofficial, importable pip API"
category = "dev"
optional = false
python-versions = ">=3.5"
[[package]]
name = "pipreqs"
version = "0.4.10"
description = "Pip requirements.txt generator based on imports in project"
category = "dev"
optional = false
python-versions = "*"
[package.dependencies]
docopt = "*"
yarg = "*"
[[package]] [[package]]
name = "pluggy" name = "pluggy"
version = "0.13.1" version = "0.13.1"
@@ -522,6 +635,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[package.extras] [package.extras]
hiredis = ["hiredis (>=0.1.3)"] hiredis = ["hiredis (>=0.1.3)"]
[[package]]
name = "regex"
version = "2021.4.4"
description = "Alternative regular expression module, to replace re."
category = "dev"
optional = false
python-versions = "*"
[[package]] [[package]]
name = "requests" name = "requests"
version = "2.25.1" version = "2.25.1"
@@ -728,6 +849,14 @@ category = "dev"
optional = false optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
name = "typed-ast"
version = "1.4.3"
description = "a fork of Python 2 and 3 ast modules with type comment support"
category = "dev"
optional = false
python-versions = "*"
[[package]] [[package]]
name = "typing-extensions" name = "typing-extensions"
version = "3.10.0.0" version = "3.10.0.0"
@@ -757,6 +886,17 @@ category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
[[package]]
name = "yarg"
version = "0.1.9"
description = "A semi hard Cornish cheese, also queries PyPI (PyPI client)"
category = "dev"
optional = false
python-versions = "*"
[package.dependencies]
requests = "*"
[[package]] [[package]]
name = "zipp" name = "zipp"
version = "3.4.1" version = "3.4.1"
@@ -773,13 +913,13 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pyt
build-backend = [] build-backend = []
requires = [] requires = []
rollbar = ["django-q-rollbar"] rollbar = ["django-q-rollbar"]
sentry = [] sentry = ["django-q-sentry"]
testing = ["django-redis", "croniter", "hiredis", "psutil", "iron-mq", "boto3", "pymongo"] testing = ["django-redis", "croniter", "hiredis", "psutil", "iron-mq", "boto3", "pymongo"]
[metadata] [metadata]
lock-version = "1.1" lock-version = "1.1"
python-versions = ">=3.6, <4" python-versions = ">=3.6.2, <4"
content-hash = "a01254ca3ea005712906e0952b82d8edb1eabfd0d75129ffa97858bc0bbaf47a" content-hash = "4f8af88fa16a4689155dff796affc021e1983e80f9b8ff8a902139c4ad3610e5"
[metadata.files] [metadata.files]
alabaster = [ alabaster = [
@@ -790,6 +930,10 @@ ansicon = [
{file = "ansicon-1.89.0-py2.py3-none-any.whl", hash = "sha256:f1def52d17f65c2c9682cf8370c03f541f410c1752d6a14029f97318e4b9dfec"}, {file = "ansicon-1.89.0-py2.py3-none-any.whl", hash = "sha256:f1def52d17f65c2c9682cf8370c03f541f410c1752d6a14029f97318e4b9dfec"},
{file = "ansicon-1.89.0.tar.gz", hash = "sha256:e4d039def5768a47e4afec8e89e83ec3ae5a26bf00ad851f914d1240b444d2b1"}, {file = "ansicon-1.89.0.tar.gz", hash = "sha256:e4d039def5768a47e4afec8e89e83ec3ae5a26bf00ad851f914d1240b444d2b1"},
] ]
appdirs = [
{file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
{file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
]
arrow = [ arrow = [
{file = "arrow-0.15.8-py2.py3-none-any.whl", hash = "sha256:271b8e05174d48e50324ed0dc5d74796c839c7e579a4f21cf1a7394665f9e94f"}, {file = "arrow-0.15.8-py2.py3-none-any.whl", hash = "sha256:271b8e05174d48e50324ed0dc5d74796c839c7e579a4f21cf1a7394665f9e94f"},
{file = "arrow-0.15.8.tar.gz", hash = "sha256:edc31dc051db12c95da9bac0271cd1027b8e36912daf6d4580af53b23e62721a"}, {file = "arrow-0.15.8.tar.gz", hash = "sha256:edc31dc051db12c95da9bac0271cd1027b8e36912daf6d4580af53b23e62721a"},
@@ -810,6 +954,10 @@ babel = [
{file = "Babel-2.9.1-py2.py3-none-any.whl", hash = "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9"}, {file = "Babel-2.9.1-py2.py3-none-any.whl", hash = "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9"},
{file = "Babel-2.9.1.tar.gz", hash = "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"}, {file = "Babel-2.9.1.tar.gz", hash = "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"},
] ]
black = [
{file = "black-21.5b1-py3-none-any.whl", hash = "sha256:8a60071a0043876a4ae96e6c69bd3a127dad2c1ca7c8083573eb82f92705d008"},
{file = "black-21.5b1.tar.gz", hash = "sha256:23695358dbcb3deafe7f0a3ad89feee5999a46be5fec21f4f1d108be0bcdb3b1"},
]
blessed = [ blessed = [
{file = "blessed-1.18.0-py2.py3-none-any.whl", hash = "sha256:5b5e2f0563d5a668c282f3f5946f7b1abb70c85829461900e607e74d7725106e"}, {file = "blessed-1.18.0-py2.py3-none-any.whl", hash = "sha256:5b5e2f0563d5a668c282f3f5946f7b1abb70c85829461900e607e74d7725106e"},
{file = "blessed-1.18.0.tar.gz", hash = "sha256:1312879f971330a1b7f2c6341f2ae7e2cbac244bfc9d0ecfbbecd4b0293bc755"}, {file = "blessed-1.18.0.tar.gz", hash = "sha256:1312879f971330a1b7f2c6341f2ae7e2cbac244bfc9d0ecfbbecd4b0293bc755"},
@@ -830,6 +978,10 @@ chardet = [
{file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"}, {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"},
{file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"}, {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"},
] ]
click = [
{file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"},
{file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"},
]
colorama = [ colorama = [
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
{file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
@@ -892,6 +1044,10 @@ croniter = [
{file = "croniter-0.3.37-py2.py3-none-any.whl", hash = "sha256:8f573a889ca9379e08c336193435c57c02698c2dd22659cdbe04fee57426d79b"}, {file = "croniter-0.3.37-py2.py3-none-any.whl", hash = "sha256:8f573a889ca9379e08c336193435c57c02698c2dd22659cdbe04fee57426d79b"},
{file = "croniter-0.3.37.tar.gz", hash = "sha256:12ced475dfc107bf7c6c1440af031f34be14cd97bbbfaf0f62221a9c11e86404"}, {file = "croniter-0.3.37.tar.gz", hash = "sha256:12ced475dfc107bf7c6c1440af031f34be14cd97bbbfaf0f62221a9c11e86404"},
] ]
dataclasses = [
{file = "dataclasses-0.8-py3-none-any.whl", hash = "sha256:0201d89fa866f68c8ebd9d08ee6ff50c0b255f8ec63a71c16fda7af82bb887bf"},
{file = "dataclasses-0.8.tar.gz", hash = "sha256:8479067f342acf957dc82ec415d355ab5edb7e7646b90dc6e2fd1d96ad084c97"},
]
django = [ django = [
{file = "Django-3.2.3-py3-none-any.whl", hash = "sha256:7e0a1393d18c16b503663752a8b6790880c5084412618990ce8a81cc908b4962"}, {file = "Django-3.2.3-py3-none-any.whl", hash = "sha256:7e0a1393d18c16b503663752a8b6790880c5084412618990ce8a81cc908b4962"},
{file = "Django-3.2.3.tar.gz", hash = "sha256:13ac78dbfd189532cad8f383a27e58e18b3d33f80009ceb476d7fcbfc5dcebd8"}, {file = "Django-3.2.3.tar.gz", hash = "sha256:13ac78dbfd189532cad8f383a27e58e18b3d33f80009ceb476d7fcbfc5dcebd8"},
@@ -911,6 +1067,9 @@ django-redis = [
{file = "django-redis-4.12.1.tar.gz", hash = "sha256:306589c7021e6468b2656edc89f62b8ba67e8d5a1c8877e2688042263daa7a63"}, {file = "django-redis-4.12.1.tar.gz", hash = "sha256:306589c7021e6468b2656edc89f62b8ba67e8d5a1c8877e2688042263daa7a63"},
{file = "django_redis-4.12.1-py3-none-any.whl", hash = "sha256:1133b26b75baa3664164c3f44b9d5d133d1b8de45d94d79f38d1adc5b1d502e5"}, {file = "django_redis-4.12.1-py3-none-any.whl", hash = "sha256:1133b26b75baa3664164c3f44b9d5d133d1b8de45d94d79f38d1adc5b1d502e5"},
] ]
docopt = [
{file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"},
]
docutils = [ docutils = [
{file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"},
{file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"},
@@ -981,6 +1140,10 @@ iron-core = [
iron-mq = [ iron-mq = [
{file = "iron-mq-0.9.tar.gz", hash = "sha256:c90441d872d9c08968343810a2ad1cca1664d80fd2ad3a3a2dbec57b7b38ecfa"}, {file = "iron-mq-0.9.tar.gz", hash = "sha256:c90441d872d9c08968343810a2ad1cca1664d80fd2ad3a3a2dbec57b7b38ecfa"},
] ]
isort = [
{file = "isort-5.8.0-py3-none-any.whl", hash = "sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d"},
{file = "isort-5.8.0.tar.gz", hash = "sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6"},
]
jinja2 = [ jinja2 = [
{file = "Jinja2-3.0.1-py3-none-any.whl", hash = "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4"}, {file = "Jinja2-3.0.1-py3-none-any.whl", hash = "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4"},
{file = "Jinja2-3.0.1.tar.gz", hash = "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"}, {file = "Jinja2-3.0.1.tar.gz", hash = "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"},
@@ -1033,6 +1196,10 @@ more-itertools = [
{file = "more-itertools-8.8.0.tar.gz", hash = "sha256:83f0308e05477c68f56ea3a888172c78ed5d5b3c282addb67508e7ba6c8f813a"}, {file = "more-itertools-8.8.0.tar.gz", hash = "sha256:83f0308e05477c68f56ea3a888172c78ed5d5b3c282addb67508e7ba6c8f813a"},
{file = "more_itertools-8.8.0-py3-none-any.whl", hash = "sha256:2cf89ec599962f2ddc4d568a05defc40e0a587fbc10d5989713638864c36be4d"}, {file = "more_itertools-8.8.0-py3-none-any.whl", hash = "sha256:2cf89ec599962f2ddc4d568a05defc40e0a587fbc10d5989713638864c36be4d"},
] ]
mypy-extensions = [
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
]
natsort = [ natsort = [
{file = "natsort-7.1.1-py3-none-any.whl", hash = "sha256:d0f4fc06ca163fa4a5ef638d9bf111c67f65eedcc7920f98dec08e489045b67e"}, {file = "natsort-7.1.1-py3-none-any.whl", hash = "sha256:d0f4fc06ca163fa4a5ef638d9bf111c67f65eedcc7920f98dec08e489045b67e"},
{file = "natsort-7.1.1.tar.gz", hash = "sha256:00c603a42365830c4722a2eb7663a25919551217ec09a243d3399fa8dd4ac403"}, {file = "natsort-7.1.1.tar.gz", hash = "sha256:00c603a42365830c4722a2eb7663a25919551217ec09a243d3399fa8dd4ac403"},
@@ -1041,6 +1208,18 @@ packaging = [
{file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"},
{file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, {file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"},
] ]
pathspec = [
{file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"},
{file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"},
]
pip-api = [
{file = "pip-api-0.0.20.tar.gz", hash = "sha256:1da0b47824c0b6c2830f2767e7304820d148475e4d08979add271da32917598e"},
{file = "pip_api-0.0.20-py3-none-any.whl", hash = "sha256:74227da7102523339d7b6e2f6e105821b04415bdb5f096e170b0ababf88d074f"},
]
pipreqs = [
{file = "pipreqs-0.4.10-py2.py3-none-any.whl", hash = "sha256:cafe42ab70628d408c147fb8944bc303355ea8f91fddca4a98d273e572e39905"},
{file = "pipreqs-0.4.10.tar.gz", hash = "sha256:9e351d644b28b98d7386b046a73806cbb3bb66b23a30e74feeb95ed9571db939"},
]
pluggy = [ pluggy = [
{file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
{file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
@@ -1177,6 +1356,49 @@ redis = [
{file = "redis-3.5.3-py2.py3-none-any.whl", hash = "sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24"}, {file = "redis-3.5.3-py2.py3-none-any.whl", hash = "sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24"},
{file = "redis-3.5.3.tar.gz", hash = "sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2"}, {file = "redis-3.5.3.tar.gz", hash = "sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2"},
] ]
regex = [
{file = "regex-2021.4.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:619d71c59a78b84d7f18891fe914446d07edd48dc8328c8e149cbe0929b4e000"},
{file = "regex-2021.4.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:47bf5bf60cf04d72bf6055ae5927a0bd9016096bf3d742fa50d9bf9f45aa0711"},
{file = "regex-2021.4.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:281d2fd05555079448537fe108d79eb031b403dac622621c78944c235f3fcf11"},
{file = "regex-2021.4.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bd28bc2e3a772acbb07787c6308e00d9626ff89e3bfcdebe87fa5afbfdedf968"},
{file = "regex-2021.4.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:7c2a1af393fcc09e898beba5dd59196edaa3116191cc7257f9224beaed3e1aa0"},
{file = "regex-2021.4.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c38c71df845e2aabb7fb0b920d11a1b5ac8526005e533a8920aea97efb8ec6a4"},
{file = "regex-2021.4.4-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:96fcd1888ab4d03adfc9303a7b3c0bd78c5412b2bfbe76db5b56d9eae004907a"},
{file = "regex-2021.4.4-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:ade17eb5d643b7fead300a1641e9f45401c98eee23763e9ed66a43f92f20b4a7"},
{file = "regex-2021.4.4-cp36-cp36m-win32.whl", hash = "sha256:e8e5b509d5c2ff12f8418006d5a90e9436766133b564db0abaec92fd27fcee29"},
{file = "regex-2021.4.4-cp36-cp36m-win_amd64.whl", hash = "sha256:11d773d75fa650cd36f68d7ca936e3c7afaae41b863b8c387a22aaa78d3c5c79"},
{file = "regex-2021.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d3029c340cfbb3ac0a71798100ccc13b97dddf373a4ae56b6a72cf70dfd53bc8"},
{file = "regex-2021.4.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:18c071c3eb09c30a264879f0d310d37fe5d3a3111662438889ae2eb6fc570c31"},
{file = "regex-2021.4.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:4c557a7b470908b1712fe27fb1ef20772b78079808c87d20a90d051660b1d69a"},
{file = "regex-2021.4.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:01afaf2ec48e196ba91b37451aa353cb7eda77efe518e481707e0515025f0cd5"},
{file = "regex-2021.4.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:3a9cd17e6e5c7eb328517969e0cb0c3d31fd329298dd0c04af99ebf42e904f82"},
{file = "regex-2021.4.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:90f11ff637fe8798933fb29f5ae1148c978cccb0452005bf4c69e13db951e765"},
{file = "regex-2021.4.4-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:919859aa909429fb5aa9cf8807f6045592c85ef56fdd30a9a3747e513db2536e"},
{file = "regex-2021.4.4-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:339456e7d8c06dd36a22e451d58ef72cef293112b559010db3d054d5560ef439"},
{file = "regex-2021.4.4-cp37-cp37m-win32.whl", hash = "sha256:67bdb9702427ceddc6ef3dc382455e90f785af4c13d495f9626861763ee13f9d"},
{file = "regex-2021.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:32e65442138b7b76dd8173ffa2cf67356b7bc1768851dded39a7a13bf9223da3"},
{file = "regex-2021.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1e1c20e29358165242928c2de1482fb2cf4ea54a6a6dea2bd7a0e0d8ee321500"},
{file = "regex-2021.4.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:314d66636c494ed9c148a42731b3834496cc9a2c4251b1661e40936814542b14"},
{file = "regex-2021.4.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6d1b01031dedf2503631d0903cb563743f397ccaf6607a5e3b19a3d76fc10480"},
{file = "regex-2021.4.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:741a9647fcf2e45f3a1cf0e24f5e17febf3efe8d4ba1281dcc3aa0459ef424dc"},
{file = "regex-2021.4.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4c46e22a0933dd783467cf32b3516299fb98cfebd895817d685130cc50cd1093"},
{file = "regex-2021.4.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:e512d8ef5ad7b898cdb2d8ee1cb09a8339e4f8be706d27eaa180c2f177248a10"},
{file = "regex-2021.4.4-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:980d7be47c84979d9136328d882f67ec5e50008681d94ecc8afa8a65ed1f4a6f"},
{file = "regex-2021.4.4-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:ce15b6d103daff8e9fee13cf7f0add05245a05d866e73926c358e871221eae87"},
{file = "regex-2021.4.4-cp38-cp38-win32.whl", hash = "sha256:a91aa8619b23b79bcbeb37abe286f2f408d2f2d6f29a17237afda55bb54e7aac"},
{file = "regex-2021.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:c0502c0fadef0d23b128605d69b58edb2c681c25d44574fc673b0e52dce71ee2"},
{file = "regex-2021.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:598585c9f0af8374c28edd609eb291b5726d7cbce16be6a8b95aa074d252ee17"},
{file = "regex-2021.4.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:ee54ff27bf0afaf4c3b3a62bcd016c12c3fdb4ec4f413391a90bd38bc3624605"},
{file = "regex-2021.4.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7d9884d86dd4dd489e981d94a65cd30d6f07203d90e98f6f657f05170f6324c9"},
{file = "regex-2021.4.4-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:bf5824bfac591ddb2c1f0a5f4ab72da28994548c708d2191e3b87dd207eb3ad7"},
{file = "regex-2021.4.4-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:563085e55b0d4fb8f746f6a335893bda5c2cef43b2f0258fe1020ab1dd874df8"},
{file = "regex-2021.4.4-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9c3db21af35e3b3c05764461b262d6f05bbca08a71a7849fd79d47ba7bc33ed"},
{file = "regex-2021.4.4-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3916d08be28a1149fb97f7728fca1f7c15d309a9f9682d89d79db75d5e52091c"},
{file = "regex-2021.4.4-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:fd45ff9293d9274c5008a2054ecef86a9bfe819a67c7be1afb65e69b405b3042"},
{file = "regex-2021.4.4-cp39-cp39-win32.whl", hash = "sha256:fa4537fb4a98fe8fde99626e4681cc644bdcf2a795038533f9f711513a862ae6"},
{file = "regex-2021.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:97f29f57d5b84e73fbaf99ab3e26134e6687348e95ef6b48cfd2c06807005a07"},
{file = "regex-2021.4.4.tar.gz", hash = "sha256:52ba3d3f9b942c49d7e4bc105bb28551c44065f139a65062ab7912bef10c9afb"},
]
requests = [ requests = [
{file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"},
{file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"},
@@ -1236,6 +1458,38 @@ toml = [
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
] ]
typed-ast = [
{file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"},
{file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"},
{file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"},
{file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"},
{file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"},
{file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"},
{file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"},
{file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"},
{file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"},
{file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"},
{file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"},
{file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"},
{file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"},
{file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"},
{file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"},
{file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"},
{file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"},
{file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"},
{file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"},
{file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"},
{file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"},
{file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"},
{file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"},
{file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"},
{file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"},
{file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"},
{file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"},
{file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"},
{file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"},
{file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"},
]
typing-extensions = [ typing-extensions = [
{file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"}, {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"},
{file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"}, {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"},
@@ -1249,6 +1503,10 @@ wcwidth = [
{file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
{file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
] ]
yarg = [
{file = "yarg-0.1.9-py2.py3-none-any.whl", hash = "sha256:4f9cebdc00fac946c9bf2783d634e538a71c7d280a4d806d45fd4dc0ef441492"},
{file = "yarg-0.1.9.tar.gz", hash = "sha256:55695bf4d1e3e7f756496c36a69ba32c40d18f821e38f61d028f6049e5e15911"},
]
zipp = [ zipp = [
{file = "zipp-3.4.1-py3-none-any.whl", hash = "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098"}, {file = "zipp-3.4.1-py3-none-any.whl", hash = "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098"},
{file = "zipp-3.4.1.tar.gz", hash = "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76"}, {file = "zipp-3.4.1.tar.gz", hash = "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76"},
+16 -8
View File
@@ -1,8 +1,9 @@
[tool.poetry] [tool.poetry]
name = "django-q" name = "django-q"
version = "1.3.6" version = "1.3.7"
description = "A multiprocessing distributed task queue for Django" description = "A multiprocessing distributed task queue for Django"
authors = ["Ilan Steemers <koed00@gmail.com>"] authors = ["Ilan Steemers <koed00@gmail.com>"]
maintainers = ["Ilan Steemers <koed00@gmail.com>"]
license = "MIT" license = "MIT"
readme = 'README.rst' readme = 'README.rst'
@@ -32,20 +33,20 @@ classifiers = [
] ]
include = ['CHANGELOG.md'] include = ['CHANGELOG.md']
[tool.poetry.plugins] # Optional super table
[tool.poetry.plugins."djangoq.errorreporters"] [tool.poetry.plugins."djangoq.errorreporters"]
"rollbar" = "django_q_rollbar:Rollbar" "rollbar" = "django_q_rollbar:Rollbar"
"sentry" = "django_q_sentry:Sentry" "sentry" = "django_q_sentry:Sentry"
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = ">=3.6, <4" python = ">=3.6.2, <4"
django = ">=2.2" django = ">=2.2"
blessed = "^1.17.6" blessed = "^1.17.6"
arrow = "^0.15.6" arrow = "^0.15.6"
django-picklefield = "^3.0.1" django-picklefield = "^3.0.1"
django-q-rollbar = { version = "^0.1", optional = true }
django-q-sentry = { version = "^0.1", optional = true }
hiredis = { version = "^1.0.1", optional = true } hiredis = { version = "^1.0.1", optional = true }
redis = { version = "^3.5.3", optional = true } redis = { version = "^3.5.3", optional = true }
psutil = { version = "^5.7.0", optional = true } psutil = { version = "^5.7.0", optional = true }
@@ -54,17 +55,24 @@ iron-mq = { version = "^0.9", optional = true }
boto3 = { version = "^1.14.12", optional = true } boto3 = { version = "^1.14.12", optional = true }
pymongo = { version = "^3.10.1", optional = true } pymongo = { version = "^3.10.1", optional = true }
croniter = { version = "^0.3.34", optional = true } croniter = { version = "^0.3.34", optional = true }
django-q-rollbar = {version = ">=0.1", optional = true}
django-q-sentry = {version = ">=0.1", optional = true}
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
pytest = "^5.4.2" pytest = "^5.4.2"
pytest-django = "^3.9.0" pytest-django = "^3.9.0"
Sphinx = "^4.0.2" Sphinx = "^4.0.2"
pytest-cov = "^2.12.0" pytest-cov = "^2.12.0"
black = { version = "^21.5b1", allow-prereleases = true }
isort = {extras = ["requirements_deprecated_finder"], version = "^5.8.0"}
[tool.poetry.extras] [tool.poetry.extras]
rollbar = ["django-q-rollbar"] requires = ["poetry_core>=1.0.0"]
sentry = ["django-q-sentry "] build-backend = ["poetry.core.masonry.api"]
requires = ["poetry>=0.12"]
build-backend = ["poetry.masonry.api"]
testing = ["django-redis", "croniter", "hiredis", "psutil", "iron-mq", "boto3", "pymongo"] testing = ["django-redis", "croniter", "hiredis", "psutil", "iron-mq", "boto3", "pymongo"]
rollbar = ["django-q-rollbar"]
sentry = ["django-q-sentry"]
[tool.isort]
profile = "black"
multi_line_output = 3
-11
View File
@@ -1,11 +0,0 @@
arrow
blessed
django-picklefield
hiredis
redis
psutil
django-redis
iron-mq
boto3
pymongo
croniter
+224 -76
View File
@@ -1,76 +1,224 @@
# ansicon==1.89.0; platform_system == "Windows" \
# This file is autogenerated by pip-compile --hash=sha256:f1def52d17f65c2c9682cf8370c03f541f410c1752d6a14029f97318e4b9dfec \
# To update, run: --hash=sha256:e4d039def5768a47e4afec8e89e83ec3ae5a26bf00ad851f914d1240b444d2b1
# arrow==0.15.8; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") \
# pip-compile requirements.in --hash=sha256:271b8e05174d48e50324ed0dc5d74796c839c7e579a4f21cf1a7394665f9e94f \
# --hash=sha256:edc31dc051db12c95da9bac0271cd1027b8e36912daf6d4580af53b23e62721a
arrow==1.1.0 asgiref==3.3.4; python_version >= "3.6" \
# via -r requirements.in --hash=sha256:92906c611ce6c967347bbfea733f13d6313901d54dcca88195eaeb52b2a8e8ee \
asgiref==3.3.4 --hash=sha256:d1216dfbdfb63826470995d31caed36225dcaf34f182e0fa257a4dd9e86f1b78
# via django blessed==1.18.0 \
blessed==1.18.0 --hash=sha256:5b5e2f0563d5a668c282f3f5946f7b1abb70c85829461900e607e74d7725106e \
# via -r requirements.in --hash=sha256:1312879f971330a1b7f2c6341f2ae7e2cbac244bfc9d0ecfbbecd4b0293bc755
boto3==1.17.78 boto3==1.17.78; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.6.0") \
# via -r requirements.in --hash=sha256:1a87855123df1f18081a5fb8c1abde28d0096a03f6f3ebb06bcfb77cdffdae5e \
botocore==1.20.78 --hash=sha256:2a5caee63d45fbdcc85e710c7f4146112f5d10b22fd0176643d2f2914cce54df
# via botocore==1.20.78; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" \
# boto3 --hash=sha256:37105b9434d73f9c4d4960ee54c8eb129120f4c6681eb16edf483f03c5e2326d \
# s3transfer --hash=sha256:e74775f9e64e975787d76390fc5ac5aba875d726bb9ece3b7bd900205b430389
certifi==2020.12.5 certifi==2020.12.5; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" \
# via requests --hash=sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830 \
chardet==4.0.0 --hash=sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c
# via requests chardet==4.0.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" \
croniter==1.0.13 --hash=sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5 \
# via -r requirements.in --hash=sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa
django-picklefield==3.0.1 croniter==0.3.37; (python_version >= "2.6" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") \
# via -r requirements.in --hash=sha256:8f573a889ca9379e08c336193435c57c02698c2dd22659cdbe04fee57426d79b \
django-redis==4.12.1 --hash=sha256:12ced475dfc107bf7c6c1440af031f34be14cd97bbbfaf0f62221a9c11e86404
# via -r requirements.in django-picklefield==3.0.1; python_version >= "3" \
django==3.2.3 --hash=sha256:15ccba592ca953b9edf9532e64640329cd47b136b7f8f10f2939caa5f9ce4287 \
# via --hash=sha256:3c702a54fde2d322fe5b2f39b8f78d9f655b8f77944ab26f703be6c0ed335a35
# django-picklefield django-redis==4.12.1; python_version >= "3.5" \
# django-redis --hash=sha256:306589c7021e6468b2656edc89f62b8ba67e8d5a1c8877e2688042263daa7a63 \
hiredis==2.0.0 --hash=sha256:1133b26b75baa3664164c3f44b9d5d133d1b8de45d94d79f38d1adc5b1d502e5
# via -r requirements.in django==3.2.3; python_version >= "3.6" \
idna==2.10 --hash=sha256:7e0a1393d18c16b503663752a8b6790880c5084412618990ce8a81cc908b4962 \
# via requests --hash=sha256:13ac78dbfd189532cad8f383a27e58e18b3d33f80009ceb476d7fcbfc5dcebd8
iron-core==1.2.0 hiredis==1.1.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") \
# via iron-mq --hash=sha256:289b31885b4996ce04cadfd5fc03d034dce8e2a8234479f7c9e23b9e245db06b \
iron-mq==0.9 --hash=sha256:7b0f63f10a166583ab744a58baad04e0f52cfea1ac27bfa1b0c21a48d1003c23 \
# via -r requirements.in --hash=sha256:6996883a8a6ff9117cbb3d6f5b0dcbbae6fb9e31e1a3e4e2f95e0214d9a1c655 \
jmespath==0.10.0 --hash=sha256:b33aea449e7f46738811fbc6f0b3177c6777a572207412bbbf6f525ffed001ae \
# via --hash=sha256:8daecd778c1da45b8bd54fd41ffcd471a86beed3d8e57a43acf7a8d63bba4058 \
# boto3 --hash=sha256:e82d6b930e02e80e5109b678c663a9ed210680ded81c1abaf54635d88d1da298 \
# botocore --hash=sha256:d2c0caffa47606d6d7c8af94ba42547bd2a441f06c74fd90a1ffe328524a6c64 \
psutil==5.8.0 --hash=sha256:47bcf3c5e6c1e87ceb86cdda2ee983fa0fe56a999e6185099b3c93a223f2fa9b \
# via -r requirements.in --hash=sha256:dcb2db95e629962db5a355047fb8aefb012df6c8ae608930d391619dbd96fd86 \
pymongo==3.11.4 --hash=sha256:7332d5c3e35154cd234fd79573736ddcf7a0ade7a986db35b6196b9171493e75 \
# via -r requirements.in --hash=sha256:6c96f64a54f030366657a54bb90b3093afc9c16c8e0dfa29fc0d6dbe169103a5 \
python-dateutil==2.8.1 --hash=sha256:b44f9421c4505c548435244d74037618f452844c5d3c67719d8a55e2613549da \
# via --hash=sha256:abfb15a6a7822f0fae681785cb38860e7a2cb1616a708d53df557b3d76c5bfd4 \
# arrow --hash=sha256:89ebf69cb19a33d625db72d2ac589d26e936b8f7628531269accf4a3196e7872 \
# botocore --hash=sha256:5b1451727f02e7acbdf6aae4e06d75f66ee82966ff9114550381c3271a90f56c \
# croniter --hash=sha256:7885b6f32c4a898e825bb7f56f36a02781ac4a951c63e4169f0afcf9c8c30dfb \
# iron-core --hash=sha256:a04901757cb0fb0f5602ac11dda48f5510f94372144d06c2563ba56c480b467c \
pytz==2021.1 --hash=sha256:3bb9b63d319402cead8bbd9dd55dca3b667d2997e9a0d8a1f9b6cc274db4baee \
# via django --hash=sha256:e0eeb9c112fec2031927a1745788a181d0eecbacbed941fc5c4f7bc3f7b273bf \
redis==3.5.3 --hash=sha256:18402d9e54fb278cb9a8c638df6f1550aca36a009d47ecf5aa263a38600f35b0 \
# via --hash=sha256:cdfd501c7ac5b198c15df800a3a34c38345f5182e5f80770caf362bccca65628 \
# -r requirements.in --hash=sha256:43b8ed3dbfd9171e44c554cb4acf4ee4505caa84c5e341858b50ea27dd2b6e12 \
# django-redis --hash=sha256:c2851deeabd96d3f6283e9c6b26e0bfed4de2dc6fb15edf913e78b79fc5909ed \
requests==2.25.1 --hash=sha256:955ba8ea73cf3ed8bd2f963b4cb9f8f0dcb27becd2f4b3dd536fd24c45533454 \
# via iron-core --hash=sha256:5263db1e2e1e8ae30500cdd75a979ff99dcc184201e6b4b820d0de74834d2323 \
s3transfer==0.4.2 --hash=sha256:e154891263306200260d7f3051982774d7b9ef35af3509d5adbbe539afd2610c \
# via boto3 --hash=sha256:964f18a59f5a64c0170f684c417f4fe3e695a536612e13074c4dd5d1c6d7c882 \
six==1.16.0 --hash=sha256:23344e3c2177baf6975fbfa361ed92eb7d36d08f454636e5054b3faa7c2aff8a \
# via --hash=sha256:b27f082f47d23cffc4cf1388b84fdc45c4ef6015f906cd7e0d988d9e35d36349 \
# blessed --hash=sha256:aa0af2deb166a5e26e0d554b824605e660039b161e37ed4f01b8d04beec184f3 \
# python-dateutil --hash=sha256:819f95d4eba3f9e484dd115ab7ab72845cf766b84286a00d4ecf76d33f1edca1 \
sqlparse==0.4.1 --hash=sha256:2c1c570ae7bf1bab304f29427e2475fe1856814312c4a1cf1cd0ee133f07a3c6 \
# via django --hash=sha256:9e9c9078a7ce07e6fce366bd818be89365a35d2e4b163268f0ca9ba7e13bb2f6 \
urllib3==1.26.4 --hash=sha256:2c227c0ed371771ffda256034427320870e8ea2e4fd0c0a618c766e7c49aad73 \
# via --hash=sha256:0a909bf501459062aa1552be1461456518f367379fdc9fdb1f2ca5e4a1fdd7c0 \
# botocore --hash=sha256:1e4cbbc3858ec7e680006e5ca590d89a5e083235988f26a004acf7244389ac01 \
# requests --hash=sha256:a7bf1492429f18d205f3a818da3ff1f242f60aa59006e53dee00b4ef592a3363 \
wcwidth==0.2.5 --hash=sha256:bcc371151d1512201d0214c36c0c150b1dc64f19c2b1a8c9cb1d7c7c15ebd93f \
# via blessed --hash=sha256:e64be68255234bb489a574c4f2f8df7029c98c81ec4d160d6cd836e7f0679390 \
--hash=sha256:8968eeaa4d37a38f8ca1f9dbe53526b69628edc9c42229a5b2f56d98bb828c1f \
--hash=sha256:b253fe4df2afea4dfa6b1fa8c5fef212aff8bcaaeb4207e81eed05cb5e4a7919 \
--hash=sha256:969843fbdfbf56cdb71da6f0bdf50f9985b8b8aeb630102945306cf10a9c6af2 \
--hash=sha256:e2e023a42dcbab8ed31f97c2bcdb980b7fbe0ada34037d87ba9d799664b58ded \
--hash=sha256:06a039208f83744a702279b894c8cf24c14fd63c59cd917dcde168b79eef0680 \
--hash=sha256:3ef2183de67b59930d2db8b8e8d4d58e00a50fcc5e92f4f678f6eed7a1c72d55 \
--hash=sha256:996021ef33e0f50b97ff2d6b5f422a0fe5577de21a8873b58a779a5ddd1c3132
idna==2.10; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" \
--hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0 \
--hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6
iron-core==1.2.0 \
--hash=sha256:38f0942e86bf72d426560a66e4e14ffafdc52da5bee9925a2ac3968b30c3725b
iron-mq==0.9 \
--hash=sha256:c90441d872d9c08968343810a2ad1cca1664d80fd2ad3a3a2dbec57b7b38ecfa
jinxed==1.1.0; platform_system == "Windows" \
--hash=sha256:6a61ccf963c16aa885304f27e6e5693783676897cea0c7f223270c8b8e78baf8 \
--hash=sha256:d8f1731f134e9e6b04d95095845ae6c10eb15cb223a5f0cabdea87d4a279c305
jmespath==0.10.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" \
--hash=sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f \
--hash=sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9
natsort==7.1.1; python_version >= "3.4" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.4" \
--hash=sha256:d0f4fc06ca163fa4a5ef638d9bf111c67f65eedcc7920f98dec08e489045b67e \
--hash=sha256:00c603a42365830c4722a2eb7663a25919551217ec09a243d3399fa8dd4ac403
psutil==5.8.0; (python_version >= "2.6" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") \
--hash=sha256:0066a82f7b1b37d334e68697faba68e5ad5e858279fd6351c8ca6024e8d6ba64 \
--hash=sha256:0ae6f386d8d297177fd288be6e8d1afc05966878704dad9847719650e44fc49c \
--hash=sha256:12d844996d6c2b1d3881cfa6fa201fd635971869a9da945cf6756105af73d2df \
--hash=sha256:02b8292609b1f7fcb34173b25e48d0da8667bc85f81d7476584d889c6e0f2131 \
--hash=sha256:6ffe81843131ee0ffa02c317186ed1e759a145267d54fdef1bc4ea5f5931ab60 \
--hash=sha256:ea313bb02e5e25224e518e4352af4bf5e062755160f77e4b1767dd5ccb65f876 \
--hash=sha256:5da29e394bdedd9144c7331192e20c1f79283fb03b06e6abd3a8ae45ffecee65 \
--hash=sha256:74fb2557d1430fff18ff0d72613c5ca30c45cdbfcddd6a5773e9fc1fe9364be8 \
--hash=sha256:74f2d0be88db96ada78756cb3a3e1b107ce8ab79f65aa885f76d7664e56928f6 \
--hash=sha256:99de3e8739258b3c3e8669cb9757c9a861b2a25ad0955f8e53ac662d66de61ac \
--hash=sha256:36b3b6c9e2a34b7d7fbae330a85bf72c30b1c827a4366a07443fc4b6270449e2 \
--hash=sha256:52de075468cd394ac98c66f9ca33b2f54ae1d9bff1ef6b67a212ee8f639ec06d \
--hash=sha256:c6a5fd10ce6b6344e616cf01cc5b849fa8103fbb5ba507b6b2dee4c11e84c935 \
--hash=sha256:61f05864b42fedc0771d6d8e49c35f07efd209ade09a5afe6a5059e7bb7bf83d \
--hash=sha256:0dd4465a039d343925cdc29023bb6960ccf4e74a65ad53e768403746a9207023 \
--hash=sha256:1bff0d07e76114ec24ee32e7f7f8d0c4b0514b3fae93e3d2aaafd65d22502394 \
--hash=sha256:fcc01e900c1d7bee2a37e5d6e4f9194760a93597c97fee89c4ae51701de03563 \
--hash=sha256:6223d07a1ae93f86451d0198a0c361032c4c93ebd4bf6d25e2fb3edfad9571ef \
--hash=sha256:d225cd8319aa1d3c85bf195c4e07d17d3cd68636b8fc97e6cf198f782f99af28 \
--hash=sha256:28ff7c95293ae74bf1ca1a79e8805fcde005c18a122ca983abf676ea3466362b \
--hash=sha256:ce8b867423291cb65cfc6d9c4955ee9bfc1e21fe03bb50e177f2b957f1c2469d \
--hash=sha256:90f31c34d25b1b3ed6c40cdd34ff122b1887a825297c017e4cbd6796dd8b672d \
--hash=sha256:6323d5d845c2785efb20aded4726636546b26d3b577aded22492908f7c1bdda7 \
--hash=sha256:245b5509968ac0bd179287d91210cd3f37add77dad385ef238b275bad35fa1c4 \
--hash=sha256:90d4091c2d30ddd0a03e0b97e6a33a48628469b99585e2ad6bf21f17423b112b \
--hash=sha256:ea372bcc129394485824ae3e3ddabe67dc0b118d262c568b4d2602a7070afdb0 \
--hash=sha256:f4634b033faf0d968bb9220dd1c793b897ab7f1189956e1aa9eae752527127d3 \
--hash=sha256:0c9ccb99ab76025f2f0bbecf341d4656e9c1351db8cc8a03ccd62e318ab4b5c6
pymongo==3.11.4 \
--hash=sha256:b7efc7e7049ef366777cfd35437c18a4166bb50a5606a1c840ee3b9624b54fc9 \
--hash=sha256:517ba47ca04a55b1f50ee8df9fd97f6c37df5537d118fb2718952b8623860466 \
--hash=sha256:225c61e08fe517aede7912937939e09adf086c8e6f7e40d4c85ad678c2c2aea3 \
--hash=sha256:e4e9db78b71db2b1684ee4ecc3e32c4600f18cdf76e6b9ae03e338e52ee4b168 \
--hash=sha256:8e0004b0393d72d76de94b4792a006cb960c1c65c7659930fbf9a81ce4341982 \
--hash=sha256:fedf0dee7a412ca6d1d6d92c158fe9cbaa8ea0cae90d268f9ccc0744de7a97d0 \
--hash=sha256:f947b359cc4769af8b49be7e37af01f05fcf15b401da2528021148e4a54426d1 \
--hash=sha256:3a3498a8326111221560e930f198b495ea6926937e249f475052ffc6893a6680 \
--hash=sha256:9a4f6e0b01df820ba9ed0b4e618ca83a1c089e48d4f268d0e00dcd49893d4549 \
--hash=sha256:d65bac5f6724d9ea6f0b5a0f0e4952fbbf209adcf6b5583b54c54bd2fcd74dc0 \
--hash=sha256:15b083d1b789b230e5ac284442d9ecb113c93f3785a6824f748befaab803b812 \
--hash=sha256:f08665d3cc5abc2f770f472a9b5f720a9b3ab0b8b3bb97c7c1487515e5653d39 \
--hash=sha256:977b1d4f868986b4ba5d03c317fde4d3b66e687d74473130cd598e3103db34fa \
--hash=sha256:510cd3bfabb63a07405b7b79fae63127e34c118b7531a2cbbafc7a24fd878594 \
--hash=sha256:071552b065e809d24c5653fcc14968cfd6fde4e279408640d5ac58e3353a3c5f \
--hash=sha256:f4ba58157e8ae33ee86fadf9062c506e535afd904f07f9be32731f4410a23b7f \
--hash=sha256:b413117210fa6d92664c3d860571e8e8727c3e8f2ff197276c5d0cb365abd3ad \
--hash=sha256:08b8723248730599c9803ae4c97b8f3f76c55219104303c88cb962a31e3bb5ee \
--hash=sha256:8a41fdc751dc4707a4fafb111c442411816a7c225ebb5cadb57599534b5d5372 \
--hash=sha256:f664ed7613b8b18f0ce5696b146776266a038c19c5cd6efffa08ecc189b01b73 \
--hash=sha256:5c36428cc4f7fae56354db7f46677fd21222fc3cb1e8829549b851172033e043 \
--hash=sha256:d0a70151d7de8a3194cdc906bcc1a42e14594787c64b0c1c9c975e5a2af3e251 \
--hash=sha256:9b9298964389c180a063a9e8bac8a80ed42de11d04166b20249bfa0a489e0e0f \
--hash=sha256:b2f41261b648cf5dee425f37ff14f4ad151c2f24b827052b402637158fd056ef \
--hash=sha256:e02beaab433fd1104b2804f909e694cfbdb6578020740a9051597adc1cd4e19f \
--hash=sha256:8898f6699f740ca93a0879ed07d8e6db02d68af889d0ebb3d13ab017e6b1af1e \
--hash=sha256:62c29bc36a6d9be68fe7b5aaf1e120b4aa66a958d1e146601fcd583eb12cae7b \
--hash=sha256:424799c71ff435094e5fb823c40eebb4500f0e048133311e9c026467e8ccebac \
--hash=sha256:3551912f5c34d8dd7c32c6bb00ae04192af47f7b9f653608f107d19c1a21a194 \
--hash=sha256:5db59223ed1e634d842a053325f85f908359c6dac9c8ddce8ef145061fae7df8 \
--hash=sha256:fea5cb1c63efe1399f0812532c7cf65458d38fd011be350bc5021dfcac39fba8 \
--hash=sha256:d4e62417e89b717a7bcd8576ac3108cd063225942cc91c5b37ff5465fdccd386 \
--hash=sha256:4c7e8c8e1e1918dcf6a652ac4b9d87164587c26fd2ce5dd81e73a5ab3b3d492f \
--hash=sha256:38a7b5140a48fc91681cdb5cb95b7cd64640b43d19259fdd707fa9d5a715f2b2 \
--hash=sha256:aff3656af2add93f290731a6b8930b23b35c0c09569150130a58192b3ec6fc61 \
--hash=sha256:03be7ad107d252bb7325d4af6309fdd2c025d08854d35f0e7abc8bf048f4245e \
--hash=sha256:6060794aac9f7b0644b299f46a9c6cbc0bc470bd01572f4134df140afd41ded6 \
--hash=sha256:73326b211e7410c8bd6a74500b1e3f392f39cf10862e243d00937e924f112c01 \
--hash=sha256:20d75ea11527331a2980ab04762a9d960bcfea9475c54bbeab777af880de61cd \
--hash=sha256:3135dd574ef1286189f3f04a36c8b7a256376914f8cbbce66b94f13125ded858 \
--hash=sha256:7c97554ea521f898753d9773891d0347ebfaddcc1dee2ad94850b163171bf1f1 \
--hash=sha256:a08c8b322b671857c81f4c30cd3c8df2895fd3c0e9358714f39e0ef8fb327702 \
--hash=sha256:f3d851af3852f16ad4adc7ee054fd9c90a7a5063de94d815b7f6a88477b9f4c6 \
--hash=sha256:3bfc7689a1bacb9bcd2f2d5185d99507aa29f667a58dd8adaa43b5a348139e46 \
--hash=sha256:b8f94acd52e530a38f25e4d5bf7ddfdd4bea9193e718f58419def0d4406b58d3 \
--hash=sha256:e4b631688dfbdd61b5610e20b64b99d25771c6d52d9da73349342d2a0f11c46a \
--hash=sha256:474e21d0e07cd09679e357d1dac76e570dab86665e79a9d3354b10a279ac6fb3 \
--hash=sha256:421d13523d11c57f57f257152bc4a6bb463aadf7a3918e9c96fefdd6be8dbfb8 \
--hash=sha256:0cabfc297f4cf921f15bc789a8fbfd7115eb9f813d3f47a74b609894bc66ab0d \
--hash=sha256:fe4189846448df013cd9df11bba38ddf78043f8c290a9f06430732a7a8601cce \
--hash=sha256:eb4d176394c37a76e8b0afe54b12d58614a67a60a7f8c0dd3a5afbb013c01092 \
--hash=sha256:fffff7bfb6799a763d3742c59c6ee7ffadda21abed557637bc44ed1080876484 \
--hash=sha256:13acf6164ead81c9fc2afa0e1ea6d6134352973ce2bb35496834fee057063c04 \
--hash=sha256:d360e5d5dd3d55bf5d1776964625018d85b937d1032bae1926dd52253decd0db \
--hash=sha256:0aaf4d44f1f819360f9432df538d54bbf850f18152f34e20337c01b828479171 \
--hash=sha256:08bda7b2c522ff9f1e554570da16298271ebb0c56ab9699446aacba249008988 \
--hash=sha256:1a994a42f49dab5b6287e499be7d3d2751776486229980d8857ad53b8333d469 \
--hash=sha256:161fcd3281c42f644aa8dec7753cca2af03ce654e17d76da4f0dab34a12480ca \
--hash=sha256:78f07961f4f214ea8e80be63cffd5cc158eb06cd922ffbf6c7155b11728f28f9 \
--hash=sha256:ad31f184dcd3271de26ab1f9c51574afb99e1b0e484ab1da3641256b723e4994 \
--hash=sha256:5e606846c049ed40940524057bfdf1105af6066688c0e6a1a3ce2038589bae70 \
--hash=sha256:3491c7de09e44eded16824cb58cf9b5cc1dc6f066a0bb7aa69929d02aa53b828 \
--hash=sha256:506a6dab4c7ffdcacdf0b8e70bd20eb2e77fa994519547c9d88d676400fcad58 \
--hash=sha256:539d4cb1b16b57026999c53e5aab857fe706e70ae5310cc8c232479923f932e6
python-dateutil==2.8.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" \
--hash=sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c \
--hash=sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a
pytz==2021.1; python_version >= "3.6" \
--hash=sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798 \
--hash=sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da
redis==3.5.3; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") \
--hash=sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24 \
--hash=sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2
requests==2.25.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" \
--hash=sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e \
--hash=sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804
s3transfer==0.4.2; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" \
--hash=sha256:9b3752887a2880690ce628bc263d6d13a3864083aeacff4890c1c9839a5eb0bc \
--hash=sha256:cb022f4b16551edebbb31a377d3f09600dbada7363d8c5db7976e7f47732e1b2
six==1.16.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" \
--hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 \
--hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926
sqlparse==0.4.1; python_version >= "3.6" \
--hash=sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0 \
--hash=sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8
typing-extensions==3.10.0.0; python_version < "3.8" and python_version >= "3.6" \
--hash=sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497 \
--hash=sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84 \
--hash=sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342
urllib3==1.26.4; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version < "4" \
--hash=sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df \
--hash=sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937
wcwidth==0.2.5 \
--hash=sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784 \
--hash=sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83
-3447
View File
File diff suppressed because it is too large Load Diff
-2
View File
@@ -1,2 +0,0 @@
[bdist_wheel]
universal=0
-71
View File
@@ -1,71 +0,0 @@
import os
from setuptools import setup, Command
with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as readme:
README = readme.read()
os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir)))
class PyTest(Command):
user_options = []
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
import subprocess
import sys
errno = subprocess.call([sys.executable, 'runtests.py'])
raise SystemExit(errno)
setup(
name='django-q',
version='1.3.6',
author='Ilan Steemers',
author_email='koed00@gmail.com',
keywords='django multiprocessing worker scheduler queue',
packages=['django_q'],
include_package_data=True,
url='https://django-q.readthedocs.org',
license='MIT',
description='A multiprocessing distributed task queue for Django',
long_description=README,
long_description_content_type='text/x-rst',
install_requires=['django>=2.2', 'django-picklefield', 'blessed', 'arrow'],
test_requires=['pytest', 'pytest-django', ],
cmdclass={'test': PyTest},
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Web Environment',
'Framework :: Django',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Operating System :: POSIX',
'Operating System :: MacOS',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Topic :: Internet :: WWW/HTTP',
'Topic :: System :: Distributed Computing',
'Topic :: Software Development :: Libraries :: Python Modules',
],
entry_points={
'djangoq.errorreporters': [
'rollbar = django_q_rollbar:Rollbar',
'sentry = django_q_sentry:Sentry',
]
},
extras_require={
'rollbar': ["django-q-rollbar>=0.1"],
'sentry': ["django-q-sentry>=0.1"],
}
)