diff --git a/.github/dependabot.yml b/.github/dependabot.yml index a72abdc..9483cee 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -10,8 +10,16 @@ updates: directory: "/" schedule: interval: "weekly" + groups: + github-actions: + patterns: + - "*" # Python - package-ecosystem: "pip" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "weekly" + groups: + pip: + patterns: + - "*" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1408c44..8328bd4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,7 +5,10 @@ on: branches: - master pull_request: - types: [opened, synchronize] + types: + - opened + - synchronize + - ready_for_review jobs: static_analysis: @@ -16,31 +19,36 @@ jobs: - uses: actions/setup-python@v5 with: python-version: 3.9 - - name: Install Dependencies and library - shell: bash - run: | - set -ux - python -m pip install --upgrade pip - pip install -e ".[dev]" + + - uses: actions/cache@v4 + id: cache + with: + path: ${{ env.pythonLocation }} + key: ${{ runner.os }}-static-analysis-${{ hashFiles('pyproject.toml') }}-test-v03 + - uses: astral-sh/setup-uv@v6 + if: steps.cache.outputs.cache-hit != 'true' + with: + version: "latest" + - name: Install Dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: uv pip install --system ".[dev]" - name: Run formatter shell: bash run: ruff format taskiq_faststream - + - name: Run ruff + shell: bash + run: ruff check taskiq_faststream - name: Run mypy shell: bash run: mypy taskiq_faststream - - name: Run ruff - shell: bash - run: ruff taskiq_faststream - test: if: github.event.pull_request.draft == false runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] fail-fast: false steps: @@ -49,14 +57,20 @@ jobs: uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} + - uses: actions/cache@v4 id: cache with: path: ${{ env.pythonLocation }} key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-test-v03 + - uses: astral-sh/setup-uv@v6 + if: steps.cache.outputs.cache-hit != 'true' + with: + version: "latest" - name: Install Dependencies if: steps.cache.outputs.cache-hit != 'true' - run: pip install -e .[test] + run: uv pip install --system ".[test]" + - run: mkdir coverage - name: Test run: bash scripts/test.sh @@ -69,6 +83,7 @@ jobs: name: .coverage.${{ runner.os }}-py${{ matrix.python-version }}-${{ matrix.pydantic-version }} path: coverage if-no-files-found: error + include-hidden-files: true coverage-combine: needs: [test] @@ -79,7 +94,11 @@ jobs: - uses: actions/setup-python@v5 with: - python-version: '3.8' + python-version: 3.9 + + - uses: astral-sh/setup-uv@v6 + with: + version: "latest" - name: Get coverage files uses: actions/download-artifact@v4 @@ -88,7 +107,7 @@ jobs: path: coverage merge-multiple: true - - run: pip install coverage[toml] + - run: uv pip install --system "coverage[toml]" - run: ls -la coverage - run: coverage combine coverage @@ -100,6 +119,8 @@ jobs: with: name: coverage-html path: htmlcov + if-no-files-found: error + include-hidden-files: true # https://github.com/marketplace/actions/alls-green#why check: # This job does nothing and is only used for the branch protection diff --git a/.gitignore b/.gitignore index 44bbb13..1a0ef5b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +uv.lock + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] @@ -143,6 +145,8 @@ venv.bak/ .dmypy.json dmypy.json +.ruff_cache/ + # Pyre type checker .pyre/ diff --git a/README.md b/README.md index ed4f298..c3789e9 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,8 @@ pip install taskiq-faststream[rabbit] pip install taskiq-faststream[kafka] # or pip install taskiq-faststream[nats] +# or +pip install taskiq-faststream[redis] ``` ## Usage diff --git a/pyproject.toml b/pyproject.toml index 50c24b4..69998c2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ authors = [ keywords = ["taskiq", "tasks", "distributed", "async", "FastStream"] -requires-python = ">=3.8" +requires-python = ">=3.9" classifiers = [ "Development Status :: 5 - Production/Stable", @@ -21,11 +21,11 @@ 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", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Operating System :: OS Independent", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Software Development :: Libraries", @@ -81,10 +81,9 @@ test = [ dev = [ "taskiq-faststream[test]", - - "mypy>=1.8.0,<1.12.0", - "ruff==0.4.1", - "pre-commit >=3.6.0,<4.0.0", + "mypy==1.11.2", + "ruff==0.11.8", + "pre-commit >=3.6.0,<5.0.0", ] [project.urls] @@ -107,7 +106,7 @@ exclude = [ ] [tool.mypy] -python_version = "3.8" +python_version = "3.9" strict = true ignore_missing_imports = true allow_subclassing_any = true @@ -125,12 +124,14 @@ known_third_party = ["faststream", "taskiq"] [tool.ruff] fix = true -target-version = "py38" +target-version = "py39" line-length = 88 -mccabe = { max-complexity = 10 } +[tool.ruff.lint] # List of enabled rulsets. # See https://docs.astral.sh/ruff/rules/ for more information. +mccabe = { max-complexity = 10 } + select = [ "E", # Error "F", # Pyflakes @@ -167,8 +168,6 @@ ignore = [ "D401", # First line should be in imperative mood "D104", # Missing docstring in public package "D100", # Missing docstring in public module - "ANN102", # Missing type annotation for self in method - "ANN101", # Missing type annotation for argument "ANN401", # typing.Any are disallowed in `**kwargs "PLR0913", # Too many arguments for function call "D106", # Missing docstring in public nested class @@ -177,7 +176,7 @@ ignore = [ ] exclude = [".venv/"] -[tool.ruff.per-file-ignores] +[tool.ruff.lint.per-file-ignores] "tests/*" = [ "S101", # Use of assert detected "S301", # Use of pickle detected @@ -187,14 +186,14 @@ exclude = [".venv/"] "D101", # Missing docstring in public class ] -[tool.ruff.pydocstyle] +[tool.ruff.lint.pydocstyle] convention = "google" ignore-decorators = ["typing.overload"] -[tool.ruff.pylint] +[tool.ruff.lint.pylint] allow-magic-value-types = ["int", "str", "float"] -[tool.ruff.flake8-bugbear] +[tool.ruff.lint.flake8-bugbear] extend-immutable-calls = [] [tool.pytest.ini_options] diff --git a/scripts/lint.sh b/scripts/lint.sh old mode 100644 new mode 100755 index 9203bc1..829cb45 --- a/scripts/lint.sh +++ b/scripts/lint.sh @@ -1,10 +1,10 @@ #!/bin/bash -echo "Running ruff..." -ruff taskiq_faststream tests --fix - echo "Running ruff formatter..." ruff format taskiq_faststream tests +echo "Running ruff..." +ruff check taskiq_faststream tests --fix + echo "Running mypy..." mypy taskiq_faststream diff --git a/taskiq_faststream/__about__.py b/taskiq_faststream/__about__.py index d9e60ec..db42d9c 100644 --- a/taskiq_faststream/__about__.py +++ b/taskiq_faststream/__about__.py @@ -1,3 +1,3 @@ """FastStream - taskiq integration to schedule FastStream tasks.""" -__version__ = "0.2.0" +__version__ = "0.2.1" diff --git a/taskiq_faststream/__init__.py b/taskiq_faststream/__init__.py index a95051d..045390d 100644 --- a/taskiq_faststream/__init__.py +++ b/taskiq_faststream/__init__.py @@ -2,7 +2,7 @@ from taskiq_faststream.scheduler import StreamScheduler __all__ = ( + "AppWrapper", "BrokerWrapper", "StreamScheduler", - "AppWrapper", ) diff --git a/taskiq_faststream/broker.py b/taskiq_faststream/broker.py index 35abc1e..4ed4e7e 100644 --- a/taskiq_faststream/broker.py +++ b/taskiq_faststream/broker.py @@ -3,12 +3,12 @@ from typing import Any import anyio -from faststream.app import FastStream +from faststream._internal.application import Application from faststream.types import SendableMessage from taskiq import AsyncBroker from taskiq.acks import AckableMessage from taskiq.decor import AsyncTaskiqDecoratedTask -from typing_extensions import TypeAlias, override +from typing_extensions import TypeAlias from taskiq_faststream.formatter import PatchedFormatter, PathcedMessage from taskiq_faststream.types import ScheduledTask @@ -66,7 +66,6 @@ async def listen( yield b"" await anyio.sleep(60) - @override def task( # type: ignore[override] self, message: typing.Union[ @@ -76,7 +75,7 @@ def task( # type: ignore[override] typing.Callable[[], typing.Awaitable[SendableMessage]], ] = None, *, - schedule: typing.List[ScheduledTask], + schedule: list[ScheduledTask], **kwargs: PublishParameters, ) -> "AsyncTaskiqDecoratedTask[[], None]": """Register FastStream scheduled task. @@ -107,7 +106,7 @@ class AppWrapper(BrokerWrapper): task : Register FastStream scheduled task. """ - def __init__(self, app: FastStream) -> None: + def __init__(self, app: Application) -> None: super(BrokerWrapper, self).__init__() self.formatter = PatchedFormatter() self.app = app diff --git a/taskiq_faststream/formatter.py b/taskiq_faststream/formatter.py index aa9bcc8..5ed1cc2 100644 --- a/taskiq_faststream/formatter.py +++ b/taskiq_faststream/formatter.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import Any, Dict +from typing import Any from taskiq.abc.formatter import TaskiqFormatter from taskiq.message import TaskiqMessage @@ -10,7 +10,7 @@ class PathcedMessage: """DTO to transfer data to `broker.kick`.""" body: Any - labels: Dict[str, Any] + labels: dict[str, Any] class PatchedFormatter(TaskiqFormatter): diff --git a/taskiq_faststream/py.typed b/taskiq_faststream/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_kafka.py b/tests/test_kafka.py index f93989b..56e65ef 100644 --- a/tests/test_kafka.py +++ b/tests/test_kafka.py @@ -23,3 +23,10 @@ class TestApp(TestBroker): def build_taskiq_broker(broker: KafkaBroker) -> AsyncBroker: """Build AppWrapper.""" return AppWrapper(FastStream(broker)) + + +class TestAsgiApp(TestBroker): + @staticmethod + def build_taskiq_broker(broker: KafkaBroker) -> AsyncBroker: + """Build AppWrapper.""" + return AppWrapper(FastStream(broker).as_asgi()) diff --git a/tests/test_nats.py b/tests/test_nats.py index a2ed69d..7c7e8c4 100644 --- a/tests/test_nats.py +++ b/tests/test_nats.py @@ -23,3 +23,10 @@ class TestApp(TestBroker): def build_taskiq_broker(broker: NatsBroker) -> AsyncBroker: """Build AppWrapper.""" return AppWrapper(FastStream(broker)) + + +class TestAsgiApp(TestBroker): + @staticmethod + def build_taskiq_broker(broker: NatsBroker) -> AsyncBroker: + """Build AppWrapper.""" + return AppWrapper(FastStream(broker).as_asgi()) diff --git a/tests/test_rabbit.py b/tests/test_rabbit.py index 5d6fdf1..f87f790 100644 --- a/tests/test_rabbit.py +++ b/tests/test_rabbit.py @@ -23,3 +23,10 @@ class TestApp(TestBroker): def build_taskiq_broker(broker: RabbitBroker) -> AsyncBroker: """Build AppWrapper.""" return AppWrapper(FastStream(broker)) + + +class TestAsgiApp(TestBroker): + @staticmethod + def build_taskiq_broker(broker: RabbitBroker) -> AsyncBroker: + """Build AppWrapper.""" + return AppWrapper(FastStream(broker).as_asgi()) diff --git a/tests/test_redis.py b/tests/test_redis.py index d08d58f..d98b9c0 100644 --- a/tests/test_redis.py +++ b/tests/test_redis.py @@ -23,3 +23,10 @@ class TestApp(TestBroker): def build_taskiq_broker(broker: RedisBroker) -> AsyncBroker: """Build AppWrapper.""" return AppWrapper(FastStream(broker)) + + +class TestAsgiApp(TestBroker): + @staticmethod + def build_taskiq_broker(broker: RedisBroker) -> AsyncBroker: + """Build AppWrapper.""" + return AppWrapper(FastStream(broker).as_asgi()) diff --git a/tests/test_resolve_message.py b/tests/test_resolve_message.py index 51c74a5..3bcdfef 100644 --- a/tests/test_resolve_message.py +++ b/tests/test_resolve_message.py @@ -1,4 +1,4 @@ -from typing import AsyncIterator, Iterator +from collections.abc import AsyncIterator, Iterator import pytest diff --git a/tests/testcase.py b/tests/testcase.py index 2aa958f..c22b7ff 100644 --- a/tests/testcase.py +++ b/tests/testcase.py @@ -1,5 +1,5 @@ import asyncio -from datetime import datetime +from datetime import datetime, timezone from typing import Any from unittest.mock import MagicMock @@ -44,7 +44,7 @@ async def handler(msg: str) -> None: **{self.subj_name: subject}, schedule=[ { - "time": datetime.utcnow(), # old python compat + "time": datetime.now(tz=timezone.utc), }, ], )