Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions crontask/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from . import _version
from .contrib import sentry

__all__ = ["cron", "scheduler", "VERSION", "__version__"]
__all__ = ["VERSION", "__version__", "cron", "scheduler"]

__version__ = _version.version
VERSION = _version.version_tuple
Expand Down Expand Up @@ -62,9 +62,11 @@ def decorator(task: Task) -> Task:
# so in Berlin Monday is 0, and Sunday is 6. We use literals to avoid
# confusion. Literals are also more readable and crontab conform.
if any(i.isdigit() for i in day_schedule):
raise ValueError(
"Please use a literal day of week (Mon, Tue, Wed, Thu, Fri, Sat, Sun) or *"
msg = (
"Please use a literal day of week "
"(Mon, Tue, Wed, Thu, Fri, Sat, Sun) or *"
)
raise ValueError(msg)
trigger = CronTrigger.from_crontab(
schedule,
timezone=timezone.get_default_timezone(),
Expand Down
3 changes: 2 additions & 1 deletion crontask/management/commands/crontask.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
def kill_softly(signum, frame):
"""Raise a KeyboardInterrupt to stop the scheduler and release the lock."""
signame = signal.Signals(signum).name
raise KeyboardInterrupt(f"Received {signame} ({signum}), shutting down…")
msg = f"Received {signame} ({signum}), shutting down…"
raise KeyboardInterrupt(msg)


class Command(BaseCommand):
Expand Down
2 changes: 1 addition & 1 deletion crontask/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def extend(self, additional_time=None, replace_ttl=False):

if redis_url := get_settings().REDIS_URL:
import redis
from redis.exceptions import LockError, LockNotOwnedError # noqa
from redis.exceptions import LockError, LockNotOwnedError

redis_client = redis.Redis.from_url(redis_url)
lock = redis_client.lock(
Expand Down
32 changes: 21 additions & 11 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -78,18 +78,28 @@ name = "crontask"
[tool.ruff]
src = [ "crontask", "tests" ]
[tool.ruff.lint]
select = [
"B", # flake8-bugbear
"C", # flake8-comprehensions
"D", # pydocstyle
"E", # pycodestyle errors
"F", # pyflakes
"I", # isort
"S", # flake8-bandit
"UP", # pyupgrade
"W", # pycodestyle warnings
extend-select = [
"C90",
"D",
"D213",
"DJ",
"E",
"G",
"N",
"PERF",
"PT",
"PTH",
"RET",
"S",
"SIM",
"SLOT",
"T20",
"TRY",
"UP",
"W",
]
ignore = [ "B904", "D1", "E501", "S101" ]
ignore = [ "D1", "D203", "D212", "E501", "RUF01" ]
per-file-ignores."tests/**" = [ "DJ006", "DJ007", "DJ008", "PLR2004", "S101", "S105", "S106", "S311", "S603", "S607" ]
isort.combine-as-imports = true
isort.force-wrap-aliases = true
isort.section-order = [ "future", "standard-library", "third-party", "first-party", "local-folder" ]
Expand Down
6 changes: 3 additions & 3 deletions tests/contrib/test_sentry.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def test_monitor_config__cron_trigger__with_day_of_week():


@pytest.mark.parametrize(
"seconds,expected",
("seconds", "expected"),
[
(60, {"type": "interval", "value": 1, "unit": "minute"}),
(300, {"type": "interval", "value": 5, "unit": "minute"}),
Expand Down Expand Up @@ -65,7 +65,7 @@ def test_monitor_config__unsupported_interval_trigger(seconds):


@pytest.mark.parametrize(
"kwargs,expected",
("kwargs", "expected"),
[
({"years": 1}, {"type": "interval", "value": 1, "unit": "year"}),
({"months": 2}, {"type": "interval", "value": 2, "unit": "month"}),
Expand All @@ -87,7 +87,7 @@ def test_monitor_config__calendar_interval_trigger(kwargs, expected):
CalendarIntervalTrigger(
months=1, days=1, timezone=timezone.get_default_timezone()
),
DateTrigger(run_date=datetime.datetime(2021, 1, 1)),
DateTrigger(run_date=datetime.datetime(2021, 1, 1, tzinfo=datetime.UTC)),
],
)
def test_monitor_config__unsupported(trigger):
Expand Down
48 changes: 27 additions & 21 deletions tests/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def test_kill_softly():


class Testcrontask:
@pytest.fixture()
@pytest.fixture
def patch_launch(self, monkeypatch):
monkeypatch.setattr(
"crontask.management.commands.crontask.Command.launch_scheduler",
Expand Down Expand Up @@ -94,10 +94,12 @@ def test_load_tasks__optional_tasks(self, patch_launch, monkeypatch):
def test_locked(self):
"""A lock was already acquired by another process."""
pytest.importorskip("redis", reason="redis is not installed")
with utils.redis_client.lock("crontask-lock", blocking_timeout=0):
with io.StringIO() as stderr:
call_command("crontask", stderr=stderr)
assert "Another scheduler is already running." in stderr.getvalue()
with (
utils.redis_client.lock("crontask-lock", blocking_timeout=0),
io.StringIO() as stderr,
):
call_command("crontask", stderr=stderr)
assert "Another scheduler is already running." in stderr.getvalue()

def test_locked_no_refresh(self, monkeypatch):
"""A lock was acquired, but it was not refreshed."""
Expand Down Expand Up @@ -143,12 +145,14 @@ def mock_signal(signum, handler):
signal_calls.append(signum)
return original_signal(signum, handler)

with patch("signal.signal", side_effect=mock_signal):
with io.StringIO() as stdout:
call_command("crontask", stdout=stdout)
assert signal.SIGHUP in signal_calls
assert signal.SIGTERM in signal_calls
assert signal.SIGINT in signal_calls
with (
patch("signal.signal", side_effect=mock_signal),
io.StringIO() as stdout,
):
call_command("crontask", stdout=stdout)
assert signal.SIGHUP in signal_calls
assert signal.SIGTERM in signal_calls
assert signal.SIGINT in signal_calls

@pytest.mark.skipif(sys.platform != "win32", reason="Windows-specific test")
def test_launch_scheduler_windows_signals(self, monkeypatch):
Expand All @@ -164,13 +168,15 @@ def mock_signal(signum, handler):
signal_calls.append(signum)
return original_signal(signum, handler)

with patch("signal.signal", side_effect=mock_signal):
with io.StringIO() as stdout:
call_command("crontask", stdout=stdout)
assert signal.SIGTERM in signal_calls
assert signal.SIGINT in signal_calls
if hasattr(signal, "SIGBREAK"):
assert signal.SIGBREAK in signal_calls
assert not any(
sig == getattr(signal, "SIGHUP", None) for sig in signal_calls
)
with (
patch("signal.signal", side_effect=mock_signal),
io.StringIO() as stdout,
):
call_command("crontask", stdout=stdout)
assert signal.SIGTERM in signal_calls
assert signal.SIGINT in signal_calls
if hasattr(signal, "SIGBREAK"):
assert signal.SIGBREAK in signal_calls
assert not any(
sig == getattr(signal, "SIGHUP", None) for sig in signal_calls
)
2 changes: 1 addition & 1 deletion tests/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ def test_cron__trigger_attribute():
)
def test_cron__error(schedule):
assert not scheduler.remove_all_jobs()
with pytest.raises(ValueError) as e:
with pytest.raises(ValueError, match="Please use a literal day of week") as e:
cron(schedule)(tasks.heartbeat)
assert (
"Please use a literal day of week (Mon, Tue, Wed, Thu, Fri, Sat, Sun) or *"
Expand Down
2 changes: 1 addition & 1 deletion tests/testapp/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = "django-insecure--@w!1qsi6azo&=a**ia3b43k^@q9aor_rqyu=1i5yd-zinpo)0" # noqa: S105
SECRET_KEY = "django-insecure--@w!1qsi6azo&=a**ia3b43k^@q9aor_rqyu=1i5yd-zinpo)0"

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
Expand Down
Loading