From e5da25d9bbb557eeb6545bcf4edd1c293599da91 Mon Sep 17 00:00:00 2001 From: Anfimov Dima Date: Tue, 4 Nov 2025 12:27:10 +0100 Subject: [PATCH 01/10] chore: rewrite dependencies in a compatible way --- poetry.lock | 2 +- pyproject.toml | 136 ++++++++++++++++++++++++++++--------------------- 2 files changed, 79 insertions(+), 59 deletions(-) diff --git a/poetry.lock b/poetry.lock index 5d34d47..c4c4dce 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2369,4 +2369,4 @@ zmq = ["pyzmq"] [metadata] lock-version = "2.1" python-versions = "^3.9" -content-hash = "2b3feaf434e86a3923bb75a66daf60dff0d43d64a1e27d6d2f269a891621af01" +content-hash = "246eaeaa34a32f480c75856ff9cc9ba0d0c22fb1a9a6750c9c1e3b855305c55e" diff --git a/pyproject.toml b/pyproject.toml index c151745..34b70bc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,14 +1,19 @@ -[tool.poetry] +[project] name = "taskiq" version = "0.0.0" description = "Distributed task queue with full async support" -authors = ["Pavel Kirilin "] -maintainers = ["Pavel Kirilin "] +authors = [ + {name = "Pavel Kirilin", email = ""} +] +maintainers = [ + {name = "Pavel Kirilin", email = ""} +] readme = "README.md" repository = "https://github.com/taskiq-python/taskiq" homepage = "https://taskiq-python.github.io/" documentation = "https://taskiq-python.github.io/" -license = "LICENSE" +license = "MIT" +license-files = ["LICENSE"] classifiers = [ "Typing :: Typed", "Programming Language :: Python", @@ -26,63 +31,78 @@ classifiers = [ "Development Status :: 3 - Alpha", ] keywords = ["taskiq", "tasks", "distributed", "async"] +requires-python = "^3.9" +dependencies = [ + "typing-extensions>=3.10.0.0", + "pydantic>=1.0,<=3.0", + "importlib-metadata", + "pycron>=3.0.0", + "taskiq_dependencies>=1.3.1,<2", + "anyio>=4", + "packaging>=19", + "pytz", + "izulu==0.50.0", + "aiohttp>=3", +] + +[project.optional-dependencies] +zmq = [ + "pyzmq>=26", +] +uv = [ + "uvloop>=0.16.0,<1; sys_platform != 'win32'", +] +metrics = [ + "prometheus_client>=0", +] +reload = [ + "watchdog>=4", + "gitignore-parser>=0", +] +orjson = [ + "orjson>=3", +] +msgpack = [ + "msgpack>=1.0.7", +] +cbor = [ + "cbor2>=5", +] + +[dependency-groups] +dev = [ + "pre-commit>=4.3.0", + # lint + "ruff>=0", + "black>=22.6.0", + # type check + "mypy>=1", + "types-mock>=4.0.15", + "types-tzlocal>=5.0.1.1", + "types-pytz>=2023.3.1.1", + # test + "pytest>=7.1.2", + "pytest-cov>=3.0.0", + "coverage>=6.4.2", + "mock>=4.0.3", + "pytest-xdist[psutil]>=2.5.0", + "tox>=4.6.4", + "freezegun>=1.2.2", + "pytest-mock>=3.11.1", + "tzlocal>=5.0.1", +] + +[project.urls] +Homepage = "https://taskiq-python.github.io/" +Documentation = "https://taskiq-python.github.io/" +Repository = "https://github.com/taskiq-python/taskiq" +"Bug Tracker" = "https://github.com/taskiq-python/taskiq/issues" +Changelog = "https://github.com/taskiq-python/taskiq/releases" -[tool.poetry.dependencies] -python = "^3.9" -typing-extensions = ">=3.10.0.0" -pydantic = ">=1.0,<=3.0" -importlib-metadata = "*" -pycron = "^3.0.0" -taskiq_dependencies = ">=1.3.1,<2" -anyio = ">=4" -packaging = ">=19" -# For prometheus metrics -prometheus_client = { version = "^0", optional = true } -# For ZMQBroker -pyzmq = { version = "^26", optional = true } -# For speed -uvloop = { version = ">=0.16.0,<1", optional = true, markers = "sys_platform != 'win32'" } -# For hot-reload. -watchdog = { version = "^4", optional = true } -gitignore-parser = { version = "^0", optional = true } -pytz = "*" -orjson = { version = "^3", optional = true } -msgpack = { version = "^1.0.7", optional = true } -cbor2 = { version = "^5", optional = true } -izulu = "0.50.0" -aiohttp = "^3" - -[tool.poetry.group.dev.dependencies] -pytest = "^7.1.2" -ruff = "^0" -black = { version = "^22.6.0", allow-prereleases = true } -mypy = "^1" -pre-commit = "^4.3.0" -coverage = "^6.4.2" -pytest-cov = "^3.0.0" -mock = "^4.0.3" -pytest-xdist = { version = "^2.5.0", extras = ["psutil"] } -types-mock = "^4.0.15" -tox = "^4.6.4" -freezegun = "^1.2.2" -pytest-mock = "^3.11.1" -tzlocal = "^5.0.1" -types-tzlocal = "^5.0.1.1" -types-pytz = "^2023.3.1.1" - -[tool.poetry.extras] -zmq = ["pyzmq"] -uv = ["uvloop"] -metrics = ["prometheus_client"] -reload = ["watchdog", "gitignore-parser"] -orjson = ["orjson"] -msgpack = ["msgpack"] -cbor = ["cbor2"] - -[tool.poetry.scripts] +[project.scripts] taskiq = "taskiq.__main__:main" -[tool.poetry.plugins.taskiq_cli] +[project.entry-points.taskiq_cli] worker = "taskiq.cli.worker.cmd:WorkerCMD" scheduler = "taskiq.cli.scheduler.cmd:SchedulerCMD" From d7a61208b153380b9a72b51187c277cbd5f1b451 Mon Sep 17 00:00:00 2001 From: Anfimov Dima Date: Tue, 4 Nov 2025 12:30:03 +0100 Subject: [PATCH 02/10] chore: remove unused pytz --- poetry.lock | 16 ++-------------- pyproject.toml | 4 +--- taskiq/cli/scheduler/run.py | 14 +++++++------- tests/cli/scheduler/test_task_delays.py | 19 ++++++++++++------- tests/scheduler/test_label_based_sched.py | 11 +++++------ tests/serializers/test_serializers.py | 3 +-- 6 files changed, 28 insertions(+), 39 deletions(-) diff --git a/poetry.lock b/poetry.lock index c4c4dce..cc1880b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1692,18 +1692,6 @@ files = [ [package.dependencies] six = ">=1.5" -[[package]] -name = "pytz" -version = "2025.2" -description = "World timezone definitions, modern and historical" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00"}, - {file = "pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3"}, -] - [[package]] name = "pyyaml" version = "6.0.2" @@ -2368,5 +2356,5 @@ zmq = ["pyzmq"] [metadata] lock-version = "2.1" -python-versions = "^3.9" -content-hash = "246eaeaa34a32f480c75856ff9cc9ba0d0c22fb1a9a6750c9c1e3b855305c55e" +python-versions = ">=3.9,<4" +content-hash = "7b11ff7c90f7d5149440c7b5b968bca20c4c1934372f440191b0ced38122adf7" diff --git a/pyproject.toml b/pyproject.toml index 34b70bc..fb6995b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,7 @@ classifiers = [ "Development Status :: 3 - Alpha", ] keywords = ["taskiq", "tasks", "distributed", "async"] -requires-python = "^3.9" +requires-python = ">=3.9,<4" dependencies = [ "typing-extensions>=3.10.0.0", "pydantic>=1.0,<=3.0", @@ -40,7 +40,6 @@ dependencies = [ "taskiq_dependencies>=1.3.1,<2", "anyio>=4", "packaging>=19", - "pytz", "izulu==0.50.0", "aiohttp>=3", ] @@ -79,7 +78,6 @@ dev = [ "mypy>=1", "types-mock>=4.0.15", "types-tzlocal>=5.0.1.1", - "types-pytz>=2023.3.1.1", # test "pytest>=7.1.2", "pytest-cov>=3.0.0", diff --git a/taskiq/cli/scheduler/run.py b/taskiq/cli/scheduler/run.py index 79b8585..0349486 100644 --- a/taskiq/cli/scheduler/run.py +++ b/taskiq/cli/scheduler/run.py @@ -4,8 +4,8 @@ from datetime import datetime, timedelta, timezone from logging import basicConfig, getLogger from typing import Any, Dict, List, Optional, Set, Tuple +from zoneinfo import ZoneInfo -import pytz from pycron import is_now from taskiq.abc.schedule_source import ScheduleSource @@ -29,7 +29,7 @@ def to_tz_aware(time: datetime) -> datetime: :return: timezone aware time. """ if time.tzinfo is None: - return time.replace(tzinfo=pytz.UTC) + return time.replace(tzinfo=timezone.utc) return time @@ -81,7 +81,7 @@ def get_task_delay(task: ScheduledTask) -> Optional[int]: :param task: task to check. :return: True if task must be sent. """ - now = datetime.now(tz=pytz.UTC) + now = datetime.now(tz=timezone.utc) if task.cron is not None: # If user specified cron offset we apply it. # If it's timedelta, we simply add the delta to current time. @@ -90,7 +90,7 @@ def get_task_delay(task: ScheduledTask) -> Optional[int]: # If timezone was specified as string we convert it timezone # offset and then apply. elif task.cron_offset and isinstance(task.cron_offset, str): - now = now.astimezone(pytz.timezone(task.cron_offset)) + now = now.astimezone(ZoneInfo(task.cron_offset)) if is_now(task.cron, now): return 0 return None @@ -159,9 +159,9 @@ async def run_scheduler_loop( # noqa: C901 loop = asyncio.get_event_loop() running_schedules: Dict[str, asyncio.Task[Any]] = {} ran_cron_jobs: Set[str] = set() - current_minute = datetime.now(tz=pytz.UTC).minute + current_minute = datetime.now(tz=timezone.utc).minute while True: - now = datetime.now(tz=pytz.UTC) + now = datetime.now(tz=timezone.utc) # If minute changed, we need to clear # ran_cron_jobs set and update current minute. if now.minute != current_minute: @@ -220,7 +220,7 @@ async def run_scheduler_loop( # noqa: C901 task_future.get_name().removeprefix("schedule_"), ), ) - delay = next_run - datetime.now(tz=pytz.UTC) + delay = next_run - datetime.now(tz=timezone.utc) logger.debug( "Sleeping for %.2f seconds before getting schedules.", delay.total_seconds(), diff --git a/tests/cli/scheduler/test_task_delays.py b/tests/cli/scheduler/test_task_delays.py index 1087fe5..82252a2 100644 --- a/tests/cli/scheduler/test_task_delays.py +++ b/tests/cli/scheduler/test_task_delays.py @@ -1,6 +1,5 @@ import datetime -import pytz from freezegun import freeze_time from tzlocal import get_localzone @@ -69,7 +68,7 @@ def test_time_utc_without_zone() -> None: def test_time_utc_with_zone() -> None: - time = datetime.datetime.now(tz=pytz.UTC) + time = datetime.datetime.now(tz=datetime.timezone.utc) delay = get_task_delay( ScheduledTask( task_name="", @@ -99,7 +98,9 @@ def test_time_utc_with_local_zone() -> None: @freeze_time("2023-01-14 12:00:00") def test_time_localtime_without_zone() -> None: - time = datetime.datetime.now(tz=pytz.FixedOffset(240)).replace(tzinfo=None) + time = datetime.datetime.now( + tz=datetime.timezone(datetime.timedelta(minutes=240)), + ).replace(tzinfo=None) time_to_run = time - datetime.timedelta(seconds=1) delay = get_task_delay( @@ -112,8 +113,10 @@ def test_time_localtime_without_zone() -> None: ), ) - expected_delay = time_to_run.replace(tzinfo=pytz.UTC) - datetime.datetime.now( - pytz.UTC, + expected_delay = time_to_run.replace( + tzinfo=datetime.timezone.utc, + ) - datetime.datetime.now( + datetime.timezone.utc, ) assert delay == int(expected_delay.total_seconds()) @@ -121,7 +124,9 @@ def test_time_localtime_without_zone() -> None: @freeze_time("2023-01-14 12:00:00") def test_time_delay() -> None: - time = datetime.datetime.now(tz=pytz.UTC) + datetime.timedelta(seconds=15) + time = datetime.datetime.now(tz=datetime.timezone.utc) + datetime.timedelta( + seconds=15, + ) delay = get_task_delay( ScheduledTask( task_name="", @@ -136,7 +141,7 @@ def test_time_delay() -> None: @freeze_time("2023-01-14 12:00:00.05") def test_time_delay_with_milliseconds() -> None: - time = datetime.datetime.now(tz=pytz.UTC) + datetime.timedelta( + time = datetime.datetime.now(tz=datetime.timezone.utc) + datetime.timedelta( seconds=15, milliseconds=150, ) diff --git a/tests/scheduler/test_label_based_sched.py b/tests/scheduler/test_label_based_sched.py index 0fab6f8..88a3bfb 100644 --- a/tests/scheduler/test_label_based_sched.py +++ b/tests/scheduler/test_label_based_sched.py @@ -1,9 +1,8 @@ import asyncio -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from typing import Any, Dict, List import pytest -import pytz from freezegun import freeze_time from taskiq.brokers.inmemory_broker import InMemoryBroker @@ -18,9 +17,9 @@ "schedule_label", [ pytest.param([{"cron": "* * * * *"}], id="cron"), - pytest.param([{"time": datetime.now(pytz.UTC)}], id="time"), + pytest.param([{"time": datetime.now(timezone.utc)}], id="time"), pytest.param( - [{"time": datetime.now(pytz.UTC), "labels": {"foo": "bar"}}], + [{"time": datetime.now(timezone.utc), "labels": {"foo": "bar"}}], id="labels_inside_schedule", ), pytest.param( @@ -92,8 +91,8 @@ async def test_task_scheduled_at_time_runs_only_once(mock_sleep: None) -> None: @broker.task( task_name="test_task", schedule=[ - {"time": datetime.now(pytz.UTC), "args": [1]}, - {"time": datetime.now(pytz.UTC) + timedelta(days=1), "args": [2]}, + {"time": datetime.now(timezone.utc), "args": [1]}, + {"time": datetime.now(timezone.utc) + timedelta(days=1), "args": [2]}, {"cron": "1 * * * *", "args": [3]}, ], ) diff --git a/tests/serializers/test_serializers.py b/tests/serializers/test_serializers.py index f2c754c..b3ad614 100644 --- a/tests/serializers/test_serializers.py +++ b/tests/serializers/test_serializers.py @@ -3,7 +3,6 @@ from typing import Any import pytest -import pytz from taskiq.abc.serializer import TaskiqSerializer from taskiq.serializers import ( @@ -62,5 +61,5 @@ def test_uuid_serialization(serializer: TaskiqSerializer) -> None: ], ) def test_datetime_serialization(serializer: TaskiqSerializer) -> None: - now = datetime.datetime.now(tz=pytz.UTC) + now = datetime.datetime.now(tz=datetime.timezone.utc) assert serializer.loadb(serializer.dumpb(now)) == now From 236c068bfb8f1a6b575110977a461939c1d3540d Mon Sep 17 00:00:00 2001 From: Anfimov Dima Date: Tue, 4 Nov 2025 12:39:53 +0100 Subject: [PATCH 03/10] chore: remove unused importlib_metadata --- poetry.lock | 46 +--------------------------------------------- pyproject.toml | 1 - taskiq/__main__.py | 3 +-- taskiq/compat.py | 2 +- 4 files changed, 3 insertions(+), 49 deletions(-) diff --git a/poetry.lock b/poetry.lock index cc1880b..f4734d3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -740,30 +740,6 @@ files = [ [package.extras] all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] -[[package]] -name = "importlib-metadata" -version = "8.6.1" -description = "Read metadata from Python packages" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e"}, - {file = "importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580"}, -] - -[package.dependencies] -zipp = ">=3.20" - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -enabler = ["pytest-enabler (>=2.2)"] -perf = ["ipython"] -test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] -type = ["pytest-mypy"] - [[package]] name = "iniconfig" version = "2.1.0" @@ -2325,26 +2301,6 @@ idna = ">=2.0" multidict = ">=4.0" propcache = ">=0.2.1" -[[package]] -name = "zipp" -version = "3.21.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, - {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, -] - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -enabler = ["pytest-enabler (>=2.2)"] -test = ["big-O", "importlib-resources ; python_version < \"3.9\"", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] -type = ["pytest-mypy"] - [extras] cbor = ["cbor2"] metrics = ["prometheus_client"] @@ -2357,4 +2313,4 @@ zmq = ["pyzmq"] [metadata] lock-version = "2.1" python-versions = ">=3.9,<4" -content-hash = "7b11ff7c90f7d5149440c7b5b968bca20c4c1934372f440191b0ced38122adf7" +content-hash = "fb7fbaa16cf11a11f9e731e8297260cda7d6977f61470a716961401655375ac1" diff --git a/pyproject.toml b/pyproject.toml index fb6995b..5fbc647 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,7 +35,6 @@ requires-python = ">=3.9,<4" dependencies = [ "typing-extensions>=3.10.0.0", "pydantic>=1.0,<=3.0", - "importlib-metadata", "pycron>=3.0.0", "taskiq_dependencies>=1.3.1,<2", "anyio>=4", diff --git a/taskiq/__main__.py b/taskiq/__main__.py index cad25c5..06b3f6d 100644 --- a/taskiq/__main__.py +++ b/taskiq/__main__.py @@ -1,9 +1,8 @@ import argparse import sys +from importlib.metadata import entry_points from typing import Dict -from importlib_metadata import entry_points - from taskiq import __version__ from taskiq.abc.cmd import TaskiqCMD diff --git a/taskiq/compat.py b/taskiq/compat.py index ce54bb9..c7e1448 100644 --- a/taskiq/compat.py +++ b/taskiq/compat.py @@ -1,9 +1,9 @@ # flake8: noqa from functools import lru_cache +from importlib.metadata import version from typing import Any, Dict, Hashable, Optional, Type, TypeVar, Union import pydantic -from importlib_metadata import version from packaging.version import Version, parse PYDANTIC_VER = parse(version("pydantic")) From 536f6bcfdbee0d234a49dfbeca58ab93e8e1ed8d Mon Sep 17 00:00:00 2001 From: Anfimov Dima Date: Tue, 4 Nov 2025 13:05:34 +0100 Subject: [PATCH 04/10] chore: mark typing-extensions as non-required with python>=3.11 --- poetry.lock | 2 +- pyproject.toml | 3 +-- taskiq/abc/broker.py | 12 ++++++++++-- taskiq/brokers/shared_broker.py | 9 +++++++-- taskiq/cli/watcher.py | 2 +- taskiq/compat.py | 2 +- taskiq/decor.py | 16 ++++++++-------- taskiq/depends/progress_tracker.py | 3 +-- taskiq/kicker.py | 7 ++++++- taskiq/result/result.py | 7 ++++++- taskiq/scheduler/scheduled_task/v2.py | 7 ++++++- taskiq/serialization.py | 4 +++- taskiq/task.py | 4 +--- 13 files changed, 52 insertions(+), 26 deletions(-) diff --git a/poetry.lock b/poetry.lock index f4734d3..f877f3e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2313,4 +2313,4 @@ zmq = ["pyzmq"] [metadata] lock-version = "2.1" python-versions = ">=3.9,<4" -content-hash = "fb7fbaa16cf11a11f9e731e8297260cda7d6977f61470a716961401655375ac1" +content-hash = "8c14c0d5091c434c50e2e04b3536df554e72d9422dde9fc1c7777a32ae1d372b" diff --git a/pyproject.toml b/pyproject.toml index 5fbc647..f3208b0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,6 @@ classifiers = [ "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -33,7 +32,7 @@ classifiers = [ keywords = ["taskiq", "tasks", "distributed", "async"] requires-python = ">=3.9,<4" dependencies = [ - "typing-extensions>=3.10.0.0", + "typing-extensions>=3.10.0.0; python_version < '3.11'", "pydantic>=1.0,<=3.0", "pycron>=3.0.0", "taskiq_dependencies>=1.3.1,<2", diff --git a/taskiq/abc/broker.py b/taskiq/abc/broker.py index f3c693a..2265a3c 100644 --- a/taskiq/abc/broker.py +++ b/taskiq/abc/broker.py @@ -23,8 +23,6 @@ ) from uuid import uuid4 -from typing_extensions import ParamSpec, Self, TypeAlias - from taskiq.abc.middleware import TaskiqMiddleware from taskiq.abc.serializer import TaskiqSerializer from taskiq.acks import AckableMessage @@ -39,6 +37,16 @@ from taskiq.utils import maybe_awaitable, remove_suffix from taskiq.warnings import TaskiqDeprecationWarning +if sys.version_info >= (3, 11): + from typing import ParamSpec, Self, TypeAlias +elif sys.version_info >= (3, 10): + from typing import ParamSpec, TypeAlias + + from typing_extensions import Self +else: + from typing_extensions import ParamSpec, Self, TypeAlias + + if TYPE_CHECKING: # pragma: no cover from taskiq.abc.formatter import TaskiqFormatter from taskiq.abc.result_backend import AsyncResultBackend diff --git a/taskiq/brokers/shared_broker.py b/taskiq/brokers/shared_broker.py index def2c79..2a18ec4 100644 --- a/taskiq/brokers/shared_broker.py +++ b/taskiq/brokers/shared_broker.py @@ -1,13 +1,18 @@ +import sys from typing import Any, AsyncGenerator, Optional, TypeVar -from typing_extensions import ParamSpec - from taskiq.abc.broker import AsyncBroker from taskiq.decor import AsyncTaskiqDecoratedTask from taskiq.exceptions import SharedBrokerListenError, SharedBrokerSendTaskError from taskiq.kicker import AsyncKicker from taskiq.message import BrokerMessage +if sys.version_info >= (3, 10): + from typing import ParamSpec +else: + from typing_extensions import ParamSpec + + _ReturnType = TypeVar("_ReturnType") _Params = ParamSpec("_Params") diff --git a/taskiq/cli/watcher.py b/taskiq/cli/watcher.py index 8c6cb29..0eee0c6 100644 --- a/taskiq/cli/watcher.py +++ b/taskiq/cli/watcher.py @@ -45,7 +45,7 @@ def dispatch(self, event: FileSystemEvent) -> None: return except Exception as exc: logger.info( - f"Cannot check path `{event.src_path}` in gitignore. Cause: {exc}", + f"Cannot check path `{event.src_path!r}` in gitignore. Cause: {exc}", ) return diff --git a/taskiq/compat.py b/taskiq/compat.py index c7e1448..f221f67 100644 --- a/taskiq/compat.py +++ b/taskiq/compat.py @@ -64,7 +64,7 @@ def model_validate_json( model_class: Type[Model], message: Union[str, bytes, bytearray], ) -> Model: - return model_class.parse_raw(message) + return model_class.parse_raw(message) # type: ignore[arg-type] def model_dump_json(instance: Model) -> str: return instance.json() diff --git a/taskiq/decor.py b/taskiq/decor.py index 6222ac7..f3152be 100644 --- a/taskiq/decor.py +++ b/taskiq/decor.py @@ -15,12 +15,15 @@ overload, ) -from typing_extensions import ParamSpec - from taskiq.kicker import AsyncKicker from taskiq.scheduler.created_schedule import CreatedSchedule from taskiq.task import AsyncTaskiqTask +if sys.version_info >= (3, 10): + from typing import ParamSpec +else: + from typing_extensions import ParamSpec + if TYPE_CHECKING: # pragma: no cover from taskiq.abc.broker import AsyncBroker from taskiq.abc.schedule_source import ScheduleSource @@ -106,24 +109,21 @@ async def kiq( self: "AsyncTaskiqDecoratedTask[_FuncParams, CoroutineType[Any, Any, _T]]", *args: _FuncParams.args, **kwargs: _FuncParams.kwargs, - ) -> AsyncTaskiqTask[_T]: - ... + ) -> AsyncTaskiqTask[_T]: ... @overload async def kiq( self: "AsyncTaskiqDecoratedTask[_FuncParams, Coroutine[Any, Any, _T]]", *args: _FuncParams.args, **kwargs: _FuncParams.kwargs, - ) -> AsyncTaskiqTask[_T]: - ... + ) -> AsyncTaskiqTask[_T]: ... @overload async def kiq( self: "AsyncTaskiqDecoratedTask[_FuncParams, _ReturnType]", *args: _FuncParams.args, **kwargs: _FuncParams.kwargs, - ) -> AsyncTaskiqTask[_ReturnType]: - ... + ) -> AsyncTaskiqTask[_ReturnType]: ... async def kiq( self, diff --git a/taskiq/depends/progress_tracker.py b/taskiq/depends/progress_tracker.py index fca71bb..4d9b3de 100644 --- a/taskiq/depends/progress_tracker.py +++ b/taskiq/depends/progress_tracker.py @@ -1,8 +1,7 @@ import enum -from typing import Generic, Optional, Union +from typing import Generic, Optional, TypeVar, Union from taskiq_dependencies import Depends -from typing_extensions import TypeVar from taskiq.compat import IS_PYDANTIC2 from taskiq.context import Context diff --git a/taskiq/kicker.py b/taskiq/kicker.py index e6b93a8..b1a9176 100644 --- a/taskiq/kicker.py +++ b/taskiq/kicker.py @@ -1,3 +1,4 @@ +import sys from collections.abc import Coroutine from dataclasses import asdict, is_dataclass from datetime import datetime @@ -16,7 +17,6 @@ ) from pydantic import BaseModel -from typing_extensions import ParamSpec from taskiq.abc.middleware import TaskiqMiddleware from taskiq.compat import model_dump @@ -28,6 +28,11 @@ from taskiq.task import AsyncTaskiqTask from taskiq.utils import maybe_awaitable +if sys.version_info >= (3, 10): + from typing import ParamSpec +else: + from typing_extensions import ParamSpec + if TYPE_CHECKING: # pragma: no cover from taskiq.abc.broker import AsyncBroker from taskiq.abc.schedule_source import ScheduleSource diff --git a/taskiq/result/result.py b/taskiq/result/result.py index 42120cf..eb06025 100644 --- a/taskiq/result/result.py +++ b/taskiq/result/result.py @@ -1,14 +1,19 @@ import json import pickle +import sys from functools import partial from typing import Any, Callable, Dict, Generic, Optional, TypeVar from pydantic import Field -from typing_extensions import Self from taskiq.compat import IS_PYDANTIC2 from taskiq.serialization import exception_to_python, prepare_exception +if sys.version_info >= (3, 11): + from typing import Self +else: + from typing_extensions import Self + _ReturnType = TypeVar("_ReturnType") diff --git a/taskiq/scheduler/scheduled_task/v2.py b/taskiq/scheduler/scheduled_task/v2.py index ce28c12..1797e2f 100644 --- a/taskiq/scheduler/scheduled_task/v2.py +++ b/taskiq/scheduler/scheduled_task/v2.py @@ -1,9 +1,14 @@ +import sys import uuid from datetime import datetime, timedelta from typing import Any, Dict, List, Optional, Union from pydantic import BaseModel, Field, model_validator -from typing_extensions import Self + +if sys.version_info >= (3, 11): + from typing import Self +else: + from typing_extensions import Self class ScheduledTask(BaseModel): diff --git a/taskiq/serialization.py b/taskiq/serialization.py index c8fd821..588c8b2 100644 --- a/taskiq/serialization.py +++ b/taskiq/serialization.py @@ -8,14 +8,16 @@ Iterable, List, Optional, + Protocol, Set, Tuple, Type, + TypeVar, Union, + runtime_checkable, ) import pydantic -from typing_extensions import Protocol, TypeVar, runtime_checkable import taskiq.exceptions from taskiq.compat import IS_PYDANTIC2, validate_call diff --git a/taskiq/task.py b/taskiq/task.py index c4c17bc..c413f21 100644 --- a/taskiq/task.py +++ b/taskiq/task.py @@ -1,9 +1,7 @@ import asyncio from logging import getLogger from time import time -from typing import TYPE_CHECKING, Any, Generic, Optional, Type - -from typing_extensions import TypeVar +from typing import TYPE_CHECKING, Any, Generic, Optional, Type, TypeVar from taskiq.compat import parse_obj_as from taskiq.exceptions import ( From ef0ed1ac811cf5004a56097e9ea0b68b89fa1d10 Mon Sep 17 00:00:00 2001 From: Anfimov Dima Date: Tue, 4 Nov 2025 13:08:08 +0100 Subject: [PATCH 05/10] chore: remove unused mock library --- poetry.lock | 29 -------------------------- pyproject.toml | 2 -- tests/middlewares/test_simple_retry.py | 2 +- tests/test_funcs.py | 2 +- 4 files changed, 2 insertions(+), 33 deletions(-) diff --git a/poetry.lock b/poetry.lock index f877f3e..8302f53 100644 --- a/poetry.lock +++ b/poetry.lock @@ -767,23 +767,6 @@ files = [ [package.extras] compatibility = ["typing-extensions (>=4.5.0)"] -[[package]] -name = "mock" -version = "4.0.3" -description = "Rolling backport of unittest.mock for all Pythons" -optional = false -python-versions = ">=3.6" -groups = ["dev"] -files = [ - {file = "mock-4.0.3-py3-none-any.whl", hash = "sha256:122fcb64ee37cfad5b3f48d7a7d51875d7031aaf3d8be7c42e2bee25044eee62"}, - {file = "mock-4.0.3.tar.gz", hash = "sha256:7d3fbbde18228f4ff2f1f119a45cdffa458b4c0dee32eb4d2bb2f82554bac7bc"}, -] - -[package.extras] -build = ["blurb", "twine", "wheel"] -docs = ["sphinx"] -test = ["pytest (<5.4)", "pytest-cov"] - [[package]] name = "msgpack" version = "1.1.0" @@ -1977,18 +1960,6 @@ virtualenv = ">=20.29.1" [package.extras] test = ["devpi-process (>=1.0.2)", "pytest (>=8.3.4)", "pytest-mock (>=3.14)"] -[[package]] -name = "types-mock" -version = "4.0.15.2" -description = "Typing stubs for mock" -optional = false -python-versions = "*" -groups = ["dev"] -files = [ - {file = "types-mock-4.0.15.2.tar.gz", hash = "sha256:83fe479741adb92210c3c92f006fe058297d5051e93c2cec36f1a9e0bae16e9e"}, - {file = "types_mock-4.0.15.2-py3-none-any.whl", hash = "sha256:39d489b6d9361b75448677680a3087701c0cfab61260363cfc0f646d2bf0a8b2"}, -] - [[package]] name = "types-pytz" version = "2023.4.0.20240130" diff --git a/pyproject.toml b/pyproject.toml index f3208b0..fa8bbff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,13 +74,11 @@ dev = [ "black>=22.6.0", # type check "mypy>=1", - "types-mock>=4.0.15", "types-tzlocal>=5.0.1.1", # test "pytest>=7.1.2", "pytest-cov>=3.0.0", "coverage>=6.4.2", - "mock>=4.0.3", "pytest-xdist[psutil]>=2.5.0", "tox>=4.6.4", "freezegun>=1.2.2", diff --git a/tests/middlewares/test_simple_retry.py b/tests/middlewares/test_simple_retry.py index 8cd6c22..783d98b 100644 --- a/tests/middlewares/test_simple_retry.py +++ b/tests/middlewares/test_simple_retry.py @@ -1,7 +1,7 @@ import uuid +from unittest.mock import AsyncMock import pytest -from mock import AsyncMock from taskiq.formatters.json_formatter import JSONFormatter from taskiq.message import TaskiqMessage diff --git a/tests/test_funcs.py b/tests/test_funcs.py index 75a2bda..e00646d 100644 --- a/tests/test_funcs.py +++ b/tests/test_funcs.py @@ -1,7 +1,7 @@ from typing import Any +from unittest.mock import AsyncMock import pytest -from mock import AsyncMock from taskiq.exceptions import ResultIsReadyError, TaskiqResultTimeoutError from taskiq.funcs import gather From 8d705daf10abcbb75b4bc678a3b5d19797c73dda Mon Sep 17 00:00:00 2001 From: Anfimov Dima Date: Tue, 4 Nov 2025 13:16:27 +0100 Subject: [PATCH 06/10] chore: remove unused pytest-mock library --- poetry.lock | 18 ------------------ pyproject.toml | 1 - tests/conftest.py | 7 ++++--- 3 files changed, 4 insertions(+), 22 deletions(-) diff --git a/poetry.lock b/poetry.lock index 8302f53..ebf8343 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1595,24 +1595,6 @@ files = [ py = "*" pytest = ">=3.10" -[[package]] -name = "pytest-mock" -version = "3.14.0" -description = "Thin-wrapper around the mock package for easier use with pytest" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, - {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, -] - -[package.dependencies] -pytest = ">=6.2.5" - -[package.extras] -dev = ["pre-commit", "pytest-asyncio", "tox"] - [[package]] name = "pytest-xdist" version = "2.5.0" diff --git a/pyproject.toml b/pyproject.toml index fa8bbff..595466e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,7 +82,6 @@ dev = [ "pytest-xdist[psutil]>=2.5.0", "tox>=4.6.4", "freezegun>=1.2.2", - "pytest-mock>=3.11.1", "tzlocal>=5.0.1", ] diff --git a/tests/conftest.py b/tests/conftest.py index 15649f7..8a652db 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,8 +1,8 @@ import asyncio from typing import Generator +from unittest.mock import patch import pytest -from pytest_mock import MockerFixture from taskiq.abc.broker import AsyncBroker @@ -33,9 +33,10 @@ def reset_broker() -> Generator[None, None, None]: @pytest.fixture -def mock_sleep(mocker: MockerFixture) -> None: +def mock_sleep() -> Generator[None, None, None]: async def _fast_sleep(delay: float) -> None: await asyncio_sleep(delay / 10000) asyncio_sleep = asyncio.sleep - mocker.patch("asyncio.sleep", _fast_sleep) + with patch("asyncio.sleep", _fast_sleep): + yield From 0ba8a843aed14c365d2e3f7b79aaf644b057aee9 Mon Sep 17 00:00:00 2001 From: Anfimov Dima Date: Tue, 4 Nov 2025 13:21:24 +0100 Subject: [PATCH 07/10] chore: remove unused tzlocal library --- pyproject.toml | 2 -- tests/cli/scheduler/test_task_delays.py | 7 +++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 595466e..4b1bd4c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,7 +74,6 @@ dev = [ "black>=22.6.0", # type check "mypy>=1", - "types-tzlocal>=5.0.1.1", # test "pytest>=7.1.2", "pytest-cov>=3.0.0", @@ -82,7 +81,6 @@ dev = [ "pytest-xdist[psutil]>=2.5.0", "tox>=4.6.4", "freezegun>=1.2.2", - "tzlocal>=5.0.1", ] [project.urls] diff --git a/tests/cli/scheduler/test_task_delays.py b/tests/cli/scheduler/test_task_delays.py index 82252a2..b005a8a 100644 --- a/tests/cli/scheduler/test_task_delays.py +++ b/tests/cli/scheduler/test_task_delays.py @@ -1,7 +1,7 @@ import datetime +from zoneinfo import ZoneInfo from freezegun import freeze_time -from tzlocal import get_localzone from taskiq.cli.scheduler.run import get_task_delay from taskiq.scheduler.scheduled_task import ScheduledTask @@ -23,7 +23,6 @@ def test_should_run_success() -> None: def test_should_run_cron_str_offset() -> None: hour = datetime.datetime.now().hour - zone = get_localzone() delay = get_task_delay( ScheduledTask( task_name="", @@ -31,7 +30,7 @@ def test_should_run_cron_str_offset() -> None: args=[], kwargs={}, cron=f"* {hour} * * *", - cron_offset=str(zone), + cron_offset=str(ZoneInfo("Europe/Paris")), ), ) assert delay is not None and delay >= 0 @@ -82,7 +81,7 @@ def test_time_utc_with_zone() -> None: def test_time_utc_with_local_zone() -> None: - localtz = get_localzone() + localtz = ZoneInfo("Europe/Paris") time = datetime.datetime.now(tz=localtz) delay = get_task_delay( ScheduledTask( From 69d20831601e134cca9901a858af6f74a3c8898f Mon Sep 17 00:00:00 2001 From: Anfimov Dima Date: Tue, 4 Nov 2025 14:46:39 +0100 Subject: [PATCH 08/10] fix: black issue in CI --- taskiq/decor.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/taskiq/decor.py b/taskiq/decor.py index f3152be..b94ba5a 100644 --- a/taskiq/decor.py +++ b/taskiq/decor.py @@ -109,21 +109,24 @@ async def kiq( self: "AsyncTaskiqDecoratedTask[_FuncParams, CoroutineType[Any, Any, _T]]", *args: _FuncParams.args, **kwargs: _FuncParams.kwargs, - ) -> AsyncTaskiqTask[_T]: ... + ) -> AsyncTaskiqTask[_T]: + ... @overload async def kiq( self: "AsyncTaskiqDecoratedTask[_FuncParams, Coroutine[Any, Any, _T]]", *args: _FuncParams.args, **kwargs: _FuncParams.kwargs, - ) -> AsyncTaskiqTask[_T]: ... + ) -> AsyncTaskiqTask[_T]: + ... @overload async def kiq( self: "AsyncTaskiqDecoratedTask[_FuncParams, _ReturnType]", *args: _FuncParams.args, **kwargs: _FuncParams.kwargs, - ) -> AsyncTaskiqTask[_ReturnType]: ... + ) -> AsyncTaskiqTask[_ReturnType]: + ... async def kiq( self, From c53cc7f7eb1bc36839df381862543ee3ae172511 Mon Sep 17 00:00:00 2001 From: Anfimov Dima Date: Tue, 4 Nov 2025 14:55:06 +0100 Subject: [PATCH 09/10] fix: tests with timezone --- tests/cli/scheduler/test_task_delays.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/cli/scheduler/test_task_delays.py b/tests/cli/scheduler/test_task_delays.py index b005a8a..1e776f0 100644 --- a/tests/cli/scheduler/test_task_delays.py +++ b/tests/cli/scheduler/test_task_delays.py @@ -22,7 +22,8 @@ def test_should_run_success() -> None: def test_should_run_cron_str_offset() -> None: - hour = datetime.datetime.now().hour + timezone = ZoneInfo("Europe/Paris") + hour = datetime.datetime.now(tz=timezone).hour delay = get_task_delay( ScheduledTask( task_name="", @@ -30,7 +31,7 @@ def test_should_run_cron_str_offset() -> None: args=[], kwargs={}, cron=f"* {hour} * * *", - cron_offset=str(ZoneInfo("Europe/Paris")), + cron_offset=str(timezone), ), ) assert delay is not None and delay >= 0 From 7edb3021c664e0b2fafe77eb4ba50a67accdd080 Mon Sep 17 00:00:00 2001 From: Anfimov Dima Date: Tue, 4 Nov 2025 14:59:27 +0100 Subject: [PATCH 10/10] fix: tests with timezone on windows --- poetry.lock | 47 +---------------------------------------------- pyproject.toml | 1 + 2 files changed, 2 insertions(+), 46 deletions(-) diff --git a/poetry.lock b/poetry.lock index ebf8343..02e8812 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1942,33 +1942,6 @@ virtualenv = ">=20.29.1" [package.extras] test = ["devpi-process (>=1.0.2)", "pytest (>=8.3.4)", "pytest-mock (>=3.14)"] -[[package]] -name = "types-pytz" -version = "2023.4.0.20240130" -description = "Typing stubs for pytz" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "types-pytz-2023.4.0.20240130.tar.gz", hash = "sha256:33676a90bf04b19f92c33eec8581136bea2f35ddd12759e579a624a006fd387a"}, - {file = "types_pytz-2023.4.0.20240130-py3-none-any.whl", hash = "sha256:6ce76a9f8fd22bd39b01a59c35bfa2db39b60d11a2f77145e97b730de7e64fe0"}, -] - -[[package]] -name = "types-tzlocal" -version = "5.1.0.1" -description = "Typing stubs for tzlocal" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "types-tzlocal-5.1.0.1.tar.gz", hash = "sha256:b84a115c0c68f0d0fa9af1c57f0645eeef0e539147806faf1f95ac3ac01ce47b"}, - {file = "types_tzlocal-5.1.0.1-py3-none-any.whl", hash = "sha256:0302e8067c86936de8f7e0aaedc2cfbf240080802c603df0f80312fbd4efb926"}, -] - -[package.dependencies] -types-pytz = "*" - [[package]] name = "typing-extensions" version = "4.12.2" @@ -1988,30 +1961,12 @@ description = "Provider of IANA time zone data" optional = false python-versions = ">=2" groups = ["dev"] -markers = "platform_system == \"Windows\"" +markers = "sys_platform == \"win32\"" files = [ {file = "tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8"}, {file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"}, ] -[[package]] -name = "tzlocal" -version = "5.3.1" -description = "tzinfo object for the local timezone" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d"}, - {file = "tzlocal-5.3.1.tar.gz", hash = "sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd"}, -] - -[package.dependencies] -tzdata = {version = "*", markers = "platform_system == \"Windows\""} - -[package.extras] -devenv = ["check-manifest", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3)", "zest.releaser"] - [[package]] name = "uvloop" version = "0.21.0" diff --git a/pyproject.toml b/pyproject.toml index 4b1bd4c..8feccd2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,6 +81,7 @@ dev = [ "pytest-xdist[psutil]>=2.5.0", "tox>=4.6.4", "freezegun>=1.2.2", + "tzdata>=2025.2; sys_platform == 'win32'", ] [project.urls]