From 69c7c84cf5f4474f1904e386500757c6d8591801 Mon Sep 17 00:00:00 2001 From: Cody Fincher Date: Mon, 30 Dec 2024 02:02:34 +0000 Subject: [PATCH 01/32] fix(picologging): updates to handle unsupported environments (python 3.13, etc.) --- litestar/logging/config.py | 7 +- litestar/logging/picologging.py | 12 +- tests/helpers.py | 15 +- .../unit/test_logging/test_logging_config.py | 215 ++++++++++++++---- 4 files changed, 189 insertions(+), 60 deletions(-) diff --git a/litestar/logging/config.py b/litestar/logging/config.py index 19615a229f..1edf357188 100644 --- a/litestar/logging/config.py +++ b/litestar/logging/config.py @@ -292,13 +292,16 @@ def configure(self) -> GetLogger: if self.logging_module == "picologging": try: - from picologging import config, getLogger + from picologging import ( # pyright: ignore[reportMissingImports,reportGeneralTypeIssues] + config, # pyright: ignore[reportMissingImports,reportGeneralTypeIssues] + getLogger, # pyright: ignore[reportMissingImports,reportGeneralTypeIssues] + ) except ImportError as e: raise MissingDependencyException("picologging") from e excluded_fields.add("incremental") else: - from logging import config, getLogger # type: ignore[no-redef, assignment] + from logging import config, getLogger # type: ignore[no-redef,assignment,unused-ignore] values = { _field.name: getattr(self, _field.name) diff --git a/litestar/logging/picologging.py b/litestar/logging/picologging.py index 2cd599f463..e07c074283 100644 --- a/litestar/logging/picologging.py +++ b/litestar/logging/picologging.py @@ -11,15 +11,15 @@ try: - import picologging # noqa: F401 + import picologging # noqa: F401 # pyright: ignore[reportMissingImports] except ImportError as e: raise MissingDependencyException("picologging") from e -from picologging import StreamHandler -from picologging.handlers import QueueHandler, QueueListener +from picologging import StreamHandler # pyright: ignore[reportMissingImports] +from picologging.handlers import QueueHandler, QueueListener # pyright: ignore[reportMissingImports] -class QueueListenerHandler(QueueHandler): +class QueueListenerHandler(QueueHandler): # type: ignore[misc,unused-ignore] """Configure queue listener and handler to support non-blocking logging configuration.""" def __init__(self, handlers: list[Any] | None = None) -> None: @@ -32,8 +32,8 @@ def __init__(self, handlers: list[Any] | None = None) -> None: - Requires ``picologging`` to be installed. """ super().__init__(Queue(-1)) - handlers = resolve_handlers(handlers) if handlers else [StreamHandler()] - self.listener = QueueListener(self.queue, *handlers) + handlers = resolve_handlers(handlers) if handlers else [StreamHandler()] # pyright: ignore[reportGeneralTypeIssues] + self.listener = QueueListener(self.queue, *handlers) # pyright: ignore[reportGeneralTypeIssues] self.listener.start() atexit.register(self.listener.stop) diff --git a/tests/helpers.py b/tests/helpers.py index b541ed28ec..353a592eb5 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -1,14 +1,16 @@ from __future__ import annotations import atexit +import importlib.util import inspect import logging import random import sys from contextlib import AbstractContextManager, contextmanager +from pathlib import Path from typing import Any, AsyncContextManager, Awaitable, ContextManager, Generator, TypeVar, cast, overload -import picologging +import pytest from _pytest.logging import LogCaptureHandler, _LiveLoggingNullHandler from litestar._openapi.schema_generation import SchemaCreator @@ -90,9 +92,9 @@ def cleanup_logging_impl() -> Generator: # Don't interfere with PyTest handler config if not isinstance(std_handler, (_LiveLoggingNullHandler, LogCaptureHandler)): std_root_logger.removeHandler(std_handler) - + picologging = pytest.importorskip("picologging") # Reset root logger (`picologging` module) - pico_root_logger: picologging.Logger = picologging.getLogger() + pico_root_logger: picologging.Logger = picologging.getLogger() # type: ignore[name-defined,unused-ignore] # pyright: ignore[reportPrivateUsage,reportGeneralTypeIssues,reportAssignmentType,reportInvalidTypeForm] for pico_handler in pico_root_logger.handlers: pico_root_logger.removeHandler(pico_handler) @@ -111,3 +113,10 @@ def cleanup_logging_impl() -> Generator: def not_none(val: T | None) -> T: assert val is not None return val + + +def purge_module(module_names: list[str], path: str | Path) -> None: + for name in module_names: + if name in sys.modules: + del sys.modules[name] + Path(importlib.util.cache_from_source(path)).unlink(missing_ok=True) # type: ignore[arg-type] diff --git a/tests/unit/test_logging/test_logging_config.py b/tests/unit/test_logging/test_logging_config.py index c4b73d8994..3423184efc 100644 --- a/tests/unit/test_logging/test_logging_config.py +++ b/tests/unit/test_logging/test_logging_config.py @@ -1,14 +1,13 @@ +import importlib import logging import sys import time from importlib.util import find_spec from logging.handlers import QueueHandler from queue import Queue -from types import ModuleType -from typing import TYPE_CHECKING, Any, Dict, Generator, Optional +from typing import TYPE_CHECKING, Any, Dict, Generator, Optional, Union, cast from unittest.mock import patch -import picologging import pytest from _pytest.logging import LogCaptureHandler, _LiveLoggingNullHandler @@ -21,9 +20,6 @@ default_handlers, default_picologging_handlers, ) -from litestar.logging.picologging import QueueListenerHandler as PicologgingQueueListenerHandler -from litestar.logging.standard import LoggingQueueListener -from litestar.logging.standard import QueueListenerHandler as StandardQueueListenerHandler from litestar.status_codes import HTTP_200_OK from litestar.testing import create_test_client from tests.helpers import cleanup_logging_impl @@ -34,6 +30,7 @@ @pytest.fixture(autouse=True) def cleanup_logging() -> Generator: + _ = pytest.importorskip("picologging") with cleanup_logging_impl(): yield @@ -123,23 +120,54 @@ def test_dictconfig_on_startup(logging_module: str, dict_config_not_called: str) @pytest.mark.parametrize( - "logging_module, expected_handler_class, expected_listener_class", + "logging_module_str, expected_handler_class_str, expected_listener_class_str", [ [ - logging, - QueueHandler if sys.version_info >= (3, 12, 0) else StandardQueueListenerHandler, - LoggingQueueListener, + "logging", + "logging.handlers.QueueHandler" + if sys.version_info >= (3, 12, 0) + else "litestar.logging.standard.QueueListenerHandler", + "litestar.logging.standard.LoggingQueueListener", ], [ - picologging, - PicologgingQueueListenerHandler, - picologging.handlers.QueueListener, # pyright: ignore[reportGeneralTypeIssues] + "picologging", + "litestar.logging.picologging.QueueListenerHandler", + "picologging.handlers.QueueListener", # pyright: ignore[reportGeneralTypeIssues,reportAttributeAccessIssue] ], ], ) def test_default_queue_listener_handler( - logging_module: ModuleType, expected_handler_class: Any, expected_listener_class: Any, capsys: "CaptureFixture[str]" + logging_module_str: str, + expected_handler_class_str: Union[str, Any], + expected_listener_class_str: str, + capsys: "CaptureFixture[str]", ) -> None: + logging_module = importlib.import_module(logging_module_str) + if expected_handler_class_str == "litestar.logging.standard.QueueListenerHandler": + from litestar.logging.standard import QueueListenerHandler + + expected_handler_class = QueueListenerHandler + elif expected_handler_class_str == "litestar.logging.picologging.QueueListenerHandler": + from litestar.logging.picologging import QueueListenerHandler + + expected_handler_class = QueueListenerHandler + elif expected_handler_class_str == "logging.handlers.QueueHandler": + from logging.handlers import QueueHandler as QueueListenerHandler + + expected_handler_class = QueueListenerHandler + else: + expected_handler_class = importlib.import_module(expected_handler_class_str) + if expected_listener_class_str == "litestar.logging.standard.LoggingQueueListener": + from litestar.logging.standard import LoggingQueueListener + + expected_listener_class = LoggingQueueListener + elif expected_listener_class_str == "picologging.handlers.QueueListener": + from picologging.handlers import QueueListener # pyright: ignore[reportMissingImports] + + expected_listener_class = QueueListener + else: + expected_listener_class = importlib.import_module(expected_listener_class_str) + def wait_log_queue(queue: Any, sleep_time: float = 0.1, max_retries: int = 5) -> None: retry = 0 while queue.qsize() > 0 and retry < max_retries: @@ -168,7 +196,7 @@ def assert_log(queue: Any, expected: str, count: Optional[int] = None) -> None: logger = get_logger("test_logger") assert type(logger) is logging_module.Logger - handler = logger.handlers[0] # pyright: ignore[reportGeneralTypeIssues] + handler = logger.handlers[0] # pyright: ignore[reportGeneralTypeIssues,reportAttributeAccessIssue] assert type(handler) is expected_handler_class assert type(handler.queue) is Queue @@ -193,14 +221,34 @@ def test_get_logger_without_logging_config() -> None: @pytest.mark.parametrize( - "logging_module, expected_handler_class", + "logging_module_str, expected_handler_class_str", [ - [logging, QueueHandler if sys.version_info >= (3, 12, 0) else StandardQueueListenerHandler], - [picologging, PicologgingQueueListenerHandler], + [ + "logging", + "logging.handlers.QueueHandler" + if sys.version_info >= (3, 12, 0) + else "litestar.logging.standard.QueueListenerHandler", + ], + ["picologging", "litestar.logging.picologging.QueueListenerHandler"], ], ) -def test_default_loggers(logging_module: ModuleType, expected_handler_class: Any) -> None: - with create_test_client(logging_config=LoggingConfig(logging_module=logging_module.__name__)) as client: +def test_default_loggers(logging_module_str: str, expected_handler_class_str: str) -> None: + logging_module = importlib.import_module(logging_module_str) + if expected_handler_class_str == "litestar.logging.standard.QueueListenerHandler": + from litestar.logging.standard import QueueListenerHandler + + expected_handler_class = QueueListenerHandler + elif expected_handler_class_str == "litestar.logging.picologging.QueueListenerHandler": + from litestar.logging.picologging import QueueListenerHandler + + expected_handler_class = QueueListenerHandler + elif expected_handler_class_str == "logging.handlers.QueueHandler": + from logging.handlers import QueueHandler as QueueListenerHandler + + expected_handler_class = QueueListenerHandler + else: + expected_handler_class = importlib.import_module(expected_handler_class_str) + with create_test_client(logging_config=LoggingConfig(logging_module=logging_module_str)) as client: root_logger = client.app.get_logger() assert isinstance(root_logger, logging_module.Logger) assert root_logger.name == "root" @@ -216,28 +264,50 @@ def test_default_loggers(logging_module: ModuleType, expected_handler_class: Any @pytest.mark.parametrize( - "logging_module, expected_handler_class", + "logging_module_str, expected_handler_class_str", [ - ["logging", QueueHandler if sys.version_info >= (3, 12, 0) else StandardQueueListenerHandler], - ["picologging", PicologgingQueueListenerHandler], + [ + "logging", + "logging.handlers.QueueHandler" + if sys.version_info >= (3, 12, 0) + else "litestar.logging.standard.QueueListenerHandler", + ], + ["picologging", "litestar.logging.picologging.QueueListenerHandler"], ], ) -def test_connection_logger(logging_module: str, expected_handler_class: Any) -> None: +def test_connection_logger(logging_module_str: str, expected_handler_class_str: str) -> None: + logging_module = importlib.import_module(logging_module_str) + if expected_handler_class_str == "litestar.logging.standard.QueueListenerHandler": + from litestar.logging.standard import QueueListenerHandler + + expected_handler_class = QueueListenerHandler + elif expected_handler_class_str == "litestar.logging.picologging.QueueListenerHandler": + from litestar.logging.picologging import QueueListenerHandler + + expected_handler_class = QueueListenerHandler + elif expected_handler_class_str == "logging.handlers.QueueHandler": + from logging.handlers import QueueHandler as QueueListenerHandler + + expected_handler_class = QueueListenerHandler + else: + expected_handler_class = importlib.import_module(expected_handler_class_str) + @get("/") def handler(request: Request) -> Dict[str, bool]: return {"isinstance": isinstance(request.logger.handlers[0], expected_handler_class)} # type: ignore[attr-defined] with create_test_client( route_handlers=[handler], - logging_config=LoggingConfig(logging_module=logging_module), + logging_config=LoggingConfig(logging_module=logging_module.__name__), ) as client: response = client.get("/") assert response.status_code == HTTP_200_OK assert response.json()["isinstance"] -@pytest.mark.parametrize("logging_module", [logging, picologging, None]) -def test_validation(logging_module: Optional[ModuleType]) -> None: +@pytest.mark.parametrize("logging_module_str", ["logging", "picologging", None]) +def test_validation(logging_module_str: Optional[str]) -> None: + logging_module = importlib.import_module(logging_module_str) if logging_module_str else None if logging_module is None: logging_config = LoggingConfig( formatters={}, @@ -267,32 +337,54 @@ def test_validation(logging_module: Optional[ModuleType]) -> None: @pytest.mark.parametrize( - "logging_module, expected_handler_class", + "logging_module_str, expected_handler_class_str", [ - [logging, QueueHandler if sys.version_info >= (3, 12, 0) else StandardQueueListenerHandler], - [picologging, PicologgingQueueListenerHandler], + [ + "logging", + "logging.handlers.QueueHandler" + if sys.version_info >= (3, 12, 0) + else "litestar.logging.standard.QueueListenerHandler", + ], + ["picologging", "litestar.logging.picologging.QueueListenerHandler"], ], ) -def test_root_logger(logging_module: ModuleType, expected_handler_class: Any) -> None: +def test_root_logger(logging_module_str: str, expected_handler_class_str: str) -> None: + logging_module = importlib.import_module(logging_module_str) + if expected_handler_class_str == "litestar.logging.standard.QueueListenerHandler": + from litestar.logging.standard import QueueListenerHandler + + expected_handler_class = QueueListenerHandler + elif expected_handler_class_str == "litestar.logging.picologging.QueueListenerHandler": + from litestar.logging.picologging import QueueListenerHandler + + expected_handler_class = QueueListenerHandler + elif expected_handler_class_str == "logging.handlers.QueueHandler": + from logging.handlers import QueueHandler as QueueListenerHandler + + expected_handler_class = QueueListenerHandler + else: + expected_handler_class = importlib.import_module(expected_handler_class_str) + logging_config = LoggingConfig(logging_module=logging_module.__name__) get_logger = logging_config.configure() root_logger = get_logger() assert root_logger.name == "root" # type: ignore[attr-defined] assert isinstance(root_logger, logging_module.Logger) - root_logger_handler = root_logger.handlers[0] # pyright: ignore[reportGeneralTypeIssues] + root_logger_handler = root_logger.handlers[0] # pyright: ignore[reportGeneralTypeIssues,reportAttributeAccessIssue] assert root_logger_handler.name == "queue_listener" - assert isinstance(root_logger_handler, expected_handler_class) + assert isinstance(root_logger_handler, cast("Any", expected_handler_class)) -@pytest.mark.parametrize("logging_module", [logging, picologging]) -def test_root_logger_no_config(logging_module: ModuleType) -> None: - logging_config = LoggingConfig(logging_module=logging_module.__name__, configure_root_logger=False) +@pytest.mark.parametrize("logging_module_str", ["logging", "picologging"]) +def test_root_logger_no_config(logging_module_str: str) -> None: + logging_module = importlib.import_module(logging_module_str) + logging_config = LoggingConfig(logging_module=logging_module_str, configure_root_logger=False) get_logger = logging_config.configure() root_logger = get_logger() assert isinstance(root_logger, logging_module.Logger) - handlers = root_logger.handlers # pyright: ignore[reportGeneralTypeIssues] + handlers = root_logger.handlers # pyright: ignore[reportGeneralTypeIssues,reportAttributeAccessIssue] if logging_module == logging: # pytest automatically configures some handlers for handler in handlers: @@ -302,20 +394,44 @@ def test_root_logger_no_config(logging_module: ModuleType) -> None: @pytest.mark.parametrize( - "logging_module, configure_root_logger, expected_root_logger_handler_class", + "logging_module_str, configure_root_logger, expected_root_logger_handler_class_str", [ - [logging, True, QueueHandler if sys.version_info >= (3, 12, 0) else StandardQueueListenerHandler], - [logging, False, None], - [picologging, True, PicologgingQueueListenerHandler], - [picologging, False, None], + [ + "logging", + True, + "logging.handlers.QueueHandler" + if sys.version_info >= (3, 12, 0) + else "litestar.logging.standard.QueueListenerHandler", + ], + ["logging", False, None], + ["picologging", True, "litestar.logging.picologging.QueueListenerHandler"], + ["picologging", False, None], ], ) def test_customizing_handler( - logging_module: ModuleType, + logging_module_str: str, configure_root_logger: bool, - expected_root_logger_handler_class: Any, + expected_root_logger_handler_class_str: "Optional[str]", capsys: "CaptureFixture[str]", ) -> None: + logging_module = importlib.import_module(logging_module_str) + if expected_root_logger_handler_class_str is None: + expected_root_logger_handler_class = None + elif expected_root_logger_handler_class_str == "litestar.logging.standard.QueueListenerHandler": + from litestar.logging.standard import QueueListenerHandler + + expected_root_logger_handler_class = QueueListenerHandler + elif expected_root_logger_handler_class_str == "litestar.logging.picologging.QueueListenerHandler": + from litestar.logging.picologging import QueueListenerHandler + + expected_root_logger_handler_class = QueueListenerHandler + elif expected_root_logger_handler_class_str == "logging.handlers.QueueHandler": + from logging.handlers import QueueHandler as QueueListenerHandler + + expected_root_logger_handler_class = QueueListenerHandler + else: + expected_root_logger_handler_class = importlib.import_module(expected_root_logger_handler_class_str) + log_format = "%(levelname)s :: %(name)s :: %(message)s" logging_config = LoggingConfig( @@ -348,7 +464,7 @@ def test_customizing_handler( # picologging seems to be broken, cannot make it log on stdout? # https://github.com/microsoft/picologging/issues/205 - if logging_module == picologging: + if logging_module_str == "picologging": del logging_config.handlers["console_stdout"]["stream"] get_logger = logging_config.configure() @@ -356,9 +472,9 @@ def test_customizing_handler( if configure_root_logger is True: assert isinstance(root_logger, logging_module.Logger) - assert root_logger.level == logging_module.INFO # pyright: ignore[reportGeneralTypeIssues] + assert root_logger.level == logging_module.INFO # pyright: ignore[reportGeneralTypeIssues,reportAttributeAccessIssue] - root_logger_handler = root_logger.handlers[0] # pyright: ignore[reportGeneralTypeIssues] + root_logger_handler = root_logger.handlers[0] # pyright: ignore[reportGeneralTypeIssues,reportAttributeAccessIssue] assert root_logger_handler.name == "queue_listener" assert type(root_logger_handler) is expected_root_logger_handler_class @@ -366,7 +482,8 @@ def test_customizing_handler( formatter = root_logger_handler.listener.handlers[0].formatter # type: ignore[attr-defined] else: formatter = root_logger_handler.formatter - assert formatter._fmt == log_format + if formatter is not None: + assert formatter._fmt == log_format else: # Root logger shouldn't be configured but pytest adds some handlers (for the standard `logging` module) for handler in root_logger.handlers: # type: ignore[attr-defined] @@ -381,7 +498,7 @@ def assert_logger(logger: Any) -> None: assert logger.handlers[0].formatter._fmt == log_format logger.info("Hello from '%s'", logging_module.__name__) - if logging_module == picologging: + if logging_module_str == "picologging": log_output = capsys.readouterr().err.strip() else: log_output = capsys.readouterr().out.strip() From ecbfc8fa97e0f449b4e071dfa71ac8f03c8a3f2e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Dec 2024 02:09:47 +0000 Subject: [PATCH 02/32] chore(deps): bump astral-sh/setup-uv from 4 to 5 (#3911) Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 4 to 5. - [Release notes](https://github.com/astral-sh/setup-uv/releases) - [Commits](https://github.com/astral-sh/setup-uv/compare/v4...v5) --- updated-dependencies: - dependency-name: astral-sh/setup-uv dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 14 +++++++------- .github/workflows/docs.yml | 2 +- .github/workflows/publish.yml | 2 +- .github/workflows/test.yml | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 31b13153b4..2e71d4d67b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,7 +45,7 @@ jobs: allow-prereleases: true - name: Install uv - uses: astral-sh/setup-uv@v4 + uses: astral-sh/setup-uv@v5 with: version: "0.5.4" enable-cache: true @@ -64,7 +64,7 @@ jobs: allow-prereleases: true - name: Install uv - uses: astral-sh/setup-uv@v4 + uses: astral-sh/setup-uv@v5 with: version: "0.5.4" enable-cache: true @@ -83,7 +83,7 @@ jobs: allow-prereleases: false - name: Install uv - uses: astral-sh/setup-uv@v4 + uses: astral-sh/setup-uv@v5 with: version: "0.5.4" enable-cache: true @@ -118,7 +118,7 @@ jobs: python-version: 3.11 - name: Install uv - uses: astral-sh/setup-uv@v4 + uses: astral-sh/setup-uv@v5 with: version: "0.5.4" enable-cache: true @@ -197,7 +197,7 @@ jobs: allow-prereleases: true - name: Install uv - uses: astral-sh/setup-uv@v4 + uses: astral-sh/setup-uv@v5 with: version: "0.5.4" enable-cache: true @@ -238,7 +238,7 @@ jobs: python-version: "3.12" - name: Install uv - uses: astral-sh/setup-uv@v4 + uses: astral-sh/setup-uv@v5 with: version: "0.5.4" enable-cache: true @@ -266,7 +266,7 @@ jobs: python-version: "3.12" - name: Install uv - uses: astral-sh/setup-uv@v4 + uses: astral-sh/setup-uv@v5 with: version: "0.5.4" enable-cache: true diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 278f034876..1785170f51 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -24,7 +24,7 @@ jobs: python-version: "3.12" - name: Install uv - uses: astral-sh/setup-uv@v4 + uses: astral-sh/setup-uv@v5 with: version: "0.5.4" enable-cache: true diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1911c553fb..3f8493b3da 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -22,7 +22,7 @@ jobs: python-version: "3.12" - name: Install uv - uses: astral-sh/setup-uv@v4 + uses: astral-sh/setup-uv@v5 with: version: "0.5.4" enable-cache: true diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 05ba777de9..d0be5130d2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -39,7 +39,7 @@ jobs: python-version: ${{ inputs.python-version }} - name: Install uv - uses: astral-sh/setup-uv@v4 + uses: astral-sh/setup-uv@v5 with: version: "0.5.4" enable-cache: true From 3e15df068e6f869c37e9ac7d483f14349b392f5d Mon Sep 17 00:00:00 2001 From: Cody Fincher Date: Mon, 30 Dec 2024 02:19:34 +0000 Subject: [PATCH 03/32] feat: enable Python 3.13 support --- .github/workflows/ci.yml | 11 +- .github/workflows/test.yml | 3 + .pre-commit-config.yaml | 2 +- pyproject.toml | 232 +++++++++++++++++++------------------ uv.lock | 102 +++++++++++++--- 5 files changed, 219 insertions(+), 131 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e71d4d67b..53d0e52c2d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -96,7 +96,7 @@ jobs: strategy: fail-fast: true matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] uses: ./.github/workflows/test.yml with: coverage: ${{ (matrix.python-version == '3.12' || matrix.python-version == '3.8') }} @@ -123,6 +123,9 @@ jobs: version: "0.5.4" enable-cache: true + - name: Install Build Dependencies + run: sudo apt-get install build-essential libpq-dev python3-dev -y + - name: Install dependencies run: | uv sync @@ -190,6 +193,9 @@ jobs: - name: Check out repository uses: actions/checkout@v4 + - name: Install Build Dependencies + run: sudo apt-get install build-essential libpq-dev python3-dev -y + - name: Set up Python uses: actions/setup-python@v5 with: @@ -260,6 +266,9 @@ jobs: - name: Check out repository uses: actions/checkout@v4 + - name: Install Build Dependencies + run: sudo apt-get install build-essential libpq-dev python3-dev -y + - name: Set up Python uses: actions/setup-python@v5 with: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d0be5130d2..4dc58cc32d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -38,6 +38,9 @@ jobs: with: python-version: ${{ inputs.python-version }} + - name: Install Build Dependencies + run: sudo apt-get install build-essential libpq-dev python3-dev -y + - name: Install uv uses: astral-sh/setup-uv@v5 with: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8dcfdb3442..15d294c3ef 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,5 @@ default_language_version: - python: "3" + python: "3.12" repos: - repo: https://github.com/compilerla/conventional-pre-commit rev: v3.6.0 diff --git a/pyproject.toml b/pyproject.toml index 2a18071ef6..7fea2d8c97 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,10 @@ [project] authors = [ - {name = "Cody Fincher", email = "cody@litestar.dev"}, - {name = "Jacob Coffee", email = "jacob@litestar.dev"}, - {name = "Janek Nouvertné", email = "janek@litestar.dev"}, - {name = "Na'aman Hirschfeld", email = "nhirschfeld@gmail.com"}, - {name = "Peter Schutt", email = "peter@litestar.dev"}, + { name = "Cody Fincher", email = "cody@litestar.dev" }, + { name = "Jacob Coffee", email = "jacob@litestar.dev" }, + { name = "Janek Nouvertné", email = "janek@litestar.dev" }, + { name = "Na'aman Hirschfeld", email = "nhirschfeld@gmail.com" }, + { name = "Peter Schutt", email = "peter@litestar.dev" }, ] classifiers = [ "Development Status :: 5 - Production/Stable", @@ -17,6 +17,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Programming Language :: Python", "Topic :: Internet :: WWW/HTTP", "Topic :: Software Development :: Libraries", @@ -31,34 +32,36 @@ classifiers = [ "Topic :: Software Development :: Libraries :: Python Modules", ] dependencies = [ - "anyio>=3", - "httpx>=0.22", - "exceptiongroup; python_version < \"3.11\"", - "importlib-metadata; python_version < \"3.10\"", - "importlib-resources>=5.12.0; python_version < \"3.9\"", - "msgspec>=0.18.2", - "multidict>=6.0.2", - "polyfactory>=2.6.3", - "pyyaml", - "typing-extensions", - "click", - "rich>=13.0.0", - "rich-click", - "multipart>=1.2.0", - # default litestar plugins - "litestar-htmx>=0.4.0" + "anyio>=3 ; python_version < \"3.9\"", + "anyio>=4 ; python_version >= \"3.9\"", + "httpx>=0.22", + "exceptiongroup; python_version < \"3.11\"", + "importlib-metadata; python_version < \"3.10\"", + "importlib-resources>=5.12.0; python_version < \"3.9\"", + "msgspec>=0.18.2,<0.19.0; python_version < \"3.9\"", + "msgspec>=0.19.0; python_version >= \"3.9\"", + "multidict>=6.0.2", + "polyfactory>=2.6.3", + "pyyaml", + "typing-extensions", + "click", + "rich>=13.0.0", + "rich-click", + "multipart>=1.2.0", + # default litestar plugins + "litestar-htmx>=0.4.0", ] description = "Litestar - A production-ready, highly performant, extensible ASGI API Framework" keywords = ["api", "rest", "asgi", "litestar", "starlite"] -license = {text = "MIT"} +license = { text = "MIT" } maintainers = [ - {name = "Litestar Developers", email = "hello@litestar.dev"}, - {name = "Cody Fincher", email = "cody@litestar.dev"}, - {name = "Jacob Coffee", email = "jacob@litestar.dev"}, - {name = "Janek Nouvertné", email = "janek@litestar.dev"}, - {name = "Peter Schutt", email = "peter@litestar.dev"}, - {name = "Visakh Unnikrishnan", email = "guacs@litestar.dev"}, - {name = "Alc", email = "alc@litestar.dev"} + { name = "Litestar Developers", email = "hello@litestar.dev" }, + { name = "Cody Fincher", email = "cody@litestar.dev" }, + { name = "Jacob Coffee", email = "jacob@litestar.dev" }, + { name = "Janek Nouvertné", email = "janek@litestar.dev" }, + { name = "Peter Schutt", email = "peter@litestar.dev" }, + { name = "Visakh Unnikrishnan", email = "guacs@litestar.dev" }, + { name = "Alc", email = "alc@litestar.dev" }, ] name = "litestar" readme = "README.md" @@ -83,35 +86,41 @@ brotli = ["brotli"] cli = ["jsbeautifier", "uvicorn[standard]", "uvloop>=0.18.0; sys_platform != 'win32'"] cryptography = ["cryptography"] full = [ - "litestar[annotated-types,attrs,brotli,cli,cryptography,jinja,jwt,mako,minijinja,opentelemetry,piccolo,picologging,prometheus,pydantic,redis,sqlalchemy,standard,structlog,valkey]", + "litestar[annotated-types,attrs,brotli,cli,cryptography,jinja,jwt,mako,minijinja,opentelemetry,piccolo,picologging,prometheus,pydantic,redis,sqlalchemy,standard,structlog,valkey]; python_version < \"3.13\"", + "litestar[annotated-types,attrs,brotli,cli,cryptography,jinja,jwt,mako,minijinja,opentelemetry,piccolo,prometheus,pydantic,redis,sqlalchemy,standard,structlog,valkey]; python_version >= \"3.13\"", ] jinja = ["jinja2>=3.1.2"] -jwt = [ - "cryptography", - "pyjwt>=2.9.0", -] +jwt = ["cryptography", "pyjwt>=2.9.0"] mako = ["mako>=1.2.4"] minijinja = ["minijinja>=1.0.0"] opentelemetry = ["opentelemetry-instrumentation-asgi"] piccolo = ["piccolo"] -picologging = ["picologging"] +picologging = ["picologging; python_version < \"3.13\""] prometheus = ["prometheus-client"] -pydantic = ["pydantic", "email-validator", "pydantic-extra-types"] +pydantic = [ + "pydantic", + "email-validator", + "pydantic-extra-types!=2.9.0; python_version < \"3.9\"", + "pydantic-extra-types; python_version >= \"3.9\"", +] redis = ["redis[hiredis]>=4.4.4"] -valkey = ["valkey[libvalkey]>=6.0.2"] sqlalchemy = ["advanced-alchemy>=0.2.2"] -standard = ["jinja2", "jsbeautifier", "uvicorn[standard]", "uvloop>=0.18.0; sys_platform != 'win32'", "fast-query-parsers>=1.0.2"] +standard = [ + "jinja2", + "jsbeautifier", + "uvicorn[standard]", + "uvloop>=0.18.0; sys_platform != 'win32'", + "fast-query-parsers>=1.0.2", +] structlog = ["structlog"] +valkey = ["valkey[libvalkey]>=6.0.2"] [project.scripts] litestar = "litestar.__main__:run_cli" [tool.hatch.build.targets.sdist] -include = [ - 'docs/PYPI_README.md', - '/litestar', -] +include = ['docs/PYPI_README.md', '/litestar'] [tool.uv] @@ -130,26 +139,27 @@ dev = [ "trio", "aiosqlite", "asyncpg>=0.29.0", - "psycopg[pool,binary]>=3.1.10,<3.2", + "psycopg[pool,binary]>=3.1.10,<3.2; python_version < \"3.13\"", + "psycopg[pool,c]; python_version >= \"3.13\"", "psycopg2-binary", "psutil>=5.9.8", "hypercorn>=0.16.0", "daphne>=4.0.0", "opentelemetry-sdk", - "httpx-sse" + "httpx-sse", ] docs = [ - "sphinx>=7.1.2", - "sphinx-autobuild>=2021.3.14", - "sphinx-copybutton>=0.5.2", - "sphinx-toolbox>=3.5.0", - "sphinx-design>=0.5.0", - "sphinx-click>=4.4.0", - "sphinxcontrib-mermaid>=0.9.2", - "auto-pytabs[sphinx]>=0.5.0", - "litestar-sphinx-theme @ git+https://github.com/litestar-org/litestar-sphinx-theme.git", - "sphinx-paramlinks>=0.6.0", + "sphinx>=7.1.2", + "sphinx-autobuild>=2021.3.14", + "sphinx-copybutton>=0.5.2", + "sphinx-toolbox>=3.5.0", + "sphinx-design>=0.5.0", + "sphinx-click>=4.4.0", + "sphinxcontrib-mermaid>=0.9.2", + "auto-pytabs[sphinx]>=0.5.0", + "litestar-sphinx-theme @ git+https://github.com/litestar-org/litestar-sphinx-theme.git", + "sphinx-paramlinks>=0.6.0", ] linting = [ "ruff>=0.2.1", @@ -193,11 +203,7 @@ plugins = ["covdefaults"] source = ["litestar"] [tool.coverage.report] -exclude_lines = [ - 'except ImportError\b', - 'if VERSION.startswith("1"):', - 'if pydantic.VERSION.startswith("1"):', -] +exclude_lines = ['except ImportError\b', 'if VERSION.startswith("1"):', 'if pydantic.VERSION.startswith("1"):'] fail_under = 50 [tool.pytest.ini_options] @@ -217,7 +223,7 @@ filterwarnings = [ "ignore::DeprecationWarning:litestar.*", "ignore::pydantic.PydanticDeprecatedSince20::", "ignore:`general_plain_validator_function`:DeprecationWarning::", - "ignore: 'RichMultiCommand':DeprecationWarning::", # this is coming from rich_click itself, nothing we can do about # that for now + "ignore: 'RichMultiCommand':DeprecationWarning::", # this is coming from rich_click itself, nothing we can do about # that for now "ignore: Dropping max_length:litestar.exceptions.LitestarWarning:litestar.contrib.piccolo", "ignore: Python Debugger on exception enabled:litestar.exceptions.LitestarWarning:", "ignore: datetime.datetime.utcnow:DeprecationWarning:time_machine", @@ -230,8 +236,6 @@ testpaths = ["tests", "docs/examples/testing"] xfail_strict = true [tool.mypy] -packages = ["litestar", "tests"] -plugins = ["pydantic.mypy"] enable_error_code = [ "truthy-bool", "truthy-iterable", @@ -240,21 +244,23 @@ enable_error_code = [ "possibly-undefined", "redundant-self", ] +packages = ["litestar", "tests"] +plugins = ["pydantic.mypy"] python_version = "3.8" disallow_any_generics = false +local_partial_types = true show_error_codes = true strict = true warn_unreachable = true -local_partial_types = true [[tool.mypy.overrides]] ignore_errors = true module = ["tests.examples.*", "tests.docker_service_fixtures"] [[tool.mypy.overrides]] -module = ["tests.*"] disable_error_code = ["truthy-bool"] +module = ["tests.*"] [[tool.mypy.overrides]] disallow_untyped_decorators = false @@ -265,12 +271,12 @@ module = ["tests.unit.test_contrib.test_repository"] strict_equality = false [[tool.mypy.overrides]] -module = ["tests.unit.test_plugins.test_pydantic.test_openapi","litestar._asgi.routing_trie.traversal"] disable_error_code = "index, union-attr" +module = ["tests.unit.test_plugins.test_pydantic.test_openapi", "litestar._asgi.routing_trie.traversal"] [[tool.mypy.overrides]] -module = ["tests.unit.test_channels.test_subscriber", "tests.unit.test_response.test_streaming_response"] disable_error_code = "arg-type, comparison-overlap, unreachable" +module = ["tests.unit.test_channels.test_subscriber", "tests.unit.test_response.test_streaming_response"] [[tool.mypy.overrides]] ignore_missing_imports = true @@ -281,10 +287,11 @@ module = [ "pytimeparse.*", "importlib_resources", "exceptiongroup", + "picologging", + "picologging.*", ] [[tool.mypy.overrides]] -warn_unused_ignores = false module = [ "litestar.contrib.sqlalchemy.*", "litestar.plugins.pydantic.*", @@ -293,17 +300,19 @@ module = [ "litestar.openapi.spec.base", "litestar.utils.helpers", "litestar.channels.plugin", - "litestar.handlers.http_handlers._utils" + "litestar.handlers.http_handlers._utils", ] +warn_unused_ignores = false [[tool.mypy.overrides]] -warn_unused_ignores = false -module = [ - "litestar.openapi.spec.base", - "litestar._asgi.routin_trie.traversal", - "litestar.plugins.pydantic.plugins.int", -] disable_error_code = "arg-type" +module = ["litestar.openapi.spec.base", "litestar._asgi.routin_trie.traversal", "litestar.plugins.pydantic.plugins.int"] +warn_unused_ignores = false + +[[tool.mypy.overrides]] +disable_error_code = "assignment" +module = ["tests.unit.test_logging.test_logging_config"] +warn_unused_ignores = false [tool.pydantic-mypy] init_forbid_extra = true @@ -330,7 +339,6 @@ pythonVersion = "3.8" reportUnnecessaryTypeIgnoreComments = true [tool.slotscheck] -strict-imports = false exclude-classes = """ ( # github.com/python/cpython/pull/106771 @@ -350,70 +358,68 @@ exclude-classes = """ |(^litestar.utils.sync:AsyncIteratorWrapper) ) """ +strict-imports = false [tool.ruff] -include = [ - "{litestar,tests,docs,test_apps,tools}/**/*.{py,pyi}", - "pyproject.toml" -] +include = ["{litestar,tests,docs,test_apps,tools}/**/*.{py,pyi}", "pyproject.toml"] lint.select = [ - "A", # flake8-builtins - "B", # flake8-bugbear + "A", # flake8-builtins + "B", # flake8-bugbear "BLE", # flake8-blind-except - "C4", # flake8-comprehensions + "C4", # flake8-comprehensions "C90", # mccabe - "D", # pydocstyle - "DJ", # flake8-django + "D", # pydocstyle + "DJ", # flake8-django "DTZ", # flake8-datetimez - "E", # pycodestyle errors + "E", # pycodestyle errors "ERA", # eradicate "EXE", # flake8-executable - "F", # pyflakes - "G", # flake8-logging-format - "I", # isort + "F", # pyflakes + "G", # flake8-logging-format + "I", # isort "ICN", # flake8-import-conventions "ISC", # flake8-implicit-str-concat - "N", # pep8-naming + "N", # pep8-naming "PIE", # flake8-pie "PLC", # pylint - convention "PLE", # pylint - error "PLW", # pylint - warning "PTH", # flake8-use-pathlib - "Q", # flake8-quotes + "Q", # flake8-quotes "RET", # flake8-return "RUF", # Ruff-specific rules - "S", # flake8-bandit + "S", # flake8-bandit "SIM", # flake8-simplify "T10", # flake8-debugger "T20", # flake8-print - "TC", # flake8-type-checking + "TC", # flake8-type-checking "TID", # flake8-tidy-imports - "UP", # pyupgrade - "W", # pycodestyle - warning + "UP", # pyupgrade + "W", # pycodestyle - warning "YTT", # flake8-2020 ] line-length = 120 lint.ignore = [ - "A003", # flake8-builtins - class attribute {name} is shadowing a python builtin - "B010", # flake8-bugbear - do not call setattr with a constant attribute value - "D100", # pydocstyle - missing docstring in public module - "D101", # pydocstyle - missing docstring in public class - "D102", # pydocstyle - missing docstring in public method - "D103", # pydocstyle - missing docstring in public function - "D104", # pydocstyle - missing docstring in public package - "D105", # pydocstyle - missing docstring in magic method - "D106", # pydocstyle - missing docstring in public nested class - "D107", # pydocstyle - missing docstring in __init__ - "D202", # pydocstyle - no blank lines allowed after function docstring - "D205", # pydocstyle - 1 blank line required between summary line and description - "D415", # pydocstyle - first line should end with a period, question mark, or exclamation point - "E501", # pycodestyle line too long, handled by ruff format + "A003", # flake8-builtins - class attribute {name} is shadowing a python builtin + "B010", # flake8-bugbear - do not call setattr with a constant attribute value + "D100", # pydocstyle - missing docstring in public module + "D101", # pydocstyle - missing docstring in public class + "D102", # pydocstyle - missing docstring in public method + "D103", # pydocstyle - missing docstring in public function + "D104", # pydocstyle - missing docstring in public package + "D105", # pydocstyle - missing docstring in magic method + "D106", # pydocstyle - missing docstring in public nested class + "D107", # pydocstyle - missing docstring in __init__ + "D202", # pydocstyle - no blank lines allowed after function docstring + "D205", # pydocstyle - 1 blank line required between summary line and description + "D415", # pydocstyle - first line should end with a period, question mark, or exclamation point + "E501", # pycodestyle line too long, handled by ruff format "PLW2901", # pylint - for loop variable overwritten by assignment target - "RUF012", # Ruff-specific rule - annotated with classvar - "ISC001", # Ruff formatter incompatible - "CPY001", # ruff - copyright notice at the top of the file + "RUF012", # Ruff-specific rule - annotated with classvar + "ISC001", # Ruff formatter incompatible + "CPY001", # ruff - copyright notice at the top of the file ] src = ["litestar", "tests", "docs/examples"] target-version = "py38" @@ -442,8 +448,8 @@ known-first-party = ["litestar", "tests", "examples"] "docs/examples/**" = ["T201"] "docs/examples/application_hooks/before_send_hook.py" = ["UP006"] "docs/examples/contrib/sqlalchemy/plugins/**/*.*" = ["UP006"] -"docs/examples/data_transfer_objects**/*.*" = ["UP006"] "docs/examples/contrib/sqlalchemy/sqlalchemy_declarative_models.py" = ["UP006"] +"docs/examples/data_transfer_objects**/*.*" = ["UP006"] "litestar/_openapi/schema_generation/schema.py" = ["C901"] "litestar/exceptions/*.*" = ["N818"] "litestar/handlers/**/*.*" = ["N801"] @@ -476,9 +482,9 @@ known-first-party = ["litestar", "tests", "examples"] "E721", ] "tests/unit/test_contrib/test_sqlalchemy/**/*.*" = ["UP006"] +"tests/unit/test_openapi/test_typescript_converter/test_converter.py" = ["W293"] "tools/**/*.*" = ["D", "ARG", "EM", "TRY", "G", "FBT"] "tools/prepare_release.py" = ["S603", "S607"] -"tests/unit/test_openapi/test_typescript_converter/test_converter.py" = ["W293"] [tool.ruff.format] docstring-code-format = true diff --git a/uv.lock b/uv.lock index 20157cb2e5..4efdb243bb 100644 --- a/uv.lock +++ b/uv.lock @@ -1,9 +1,11 @@ version = 1 requires-python = ">=3.8, <4.0" resolution-markers = [ - "python_full_version < '3.13' and sys_platform != 'win32'", + "python_full_version < '3.9' and sys_platform != 'win32'", + "python_full_version >= '3.9' and python_full_version < '3.13' and sys_platform != 'win32'", "python_full_version >= '3.13' and sys_platform != 'win32'", - "python_full_version < '3.13' and sys_platform == 'win32'", + "python_full_version < '3.9' and sys_platform == 'win32'", + "python_full_version >= '3.9' and python_full_version < '3.13' and sys_platform == 'win32'", "python_full_version >= '3.13' and sys_platform == 'win32'", ] @@ -851,7 +853,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/98/65/13d9e76ca19b0ba5603d71ac8424b5694415b348e719db277b5edc985ff5/cryptography-44.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb", size = 3915420 }, { url = "https://files.pythonhosted.org/packages/b1/07/40fe09ce96b91fc9276a9ad272832ead0fddedcba87f1190372af8e3039c/cryptography-44.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b", size = 4154498 }, { url = "https://files.pythonhosted.org/packages/75/ea/af65619c800ec0a7e4034207aec543acdf248d9bffba0533342d1bd435e1/cryptography-44.0.0-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543", size = 3932569 }, - { url = "https://files.pythonhosted.org/packages/4e/d5/9cc182bf24c86f542129565976c21301d4ac397e74bf5a16e48241aab8a6/cryptography-44.0.0-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:60eb32934076fa07e4316b7b2742fa52cbb190b42c2df2863dbc4230a0a9b385", size = 4164756 }, { url = "https://files.pythonhosted.org/packages/c7/af/d1deb0c04d59612e3d5e54203159e284d3e7a6921e565bb0eeb6269bdd8a/cryptography-44.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e", size = 4016721 }, { url = "https://files.pythonhosted.org/packages/bd/69/7ca326c55698d0688db867795134bdfac87136b80ef373aaa42b225d6dd5/cryptography-44.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e", size = 4240915 }, { url = "https://files.pythonhosted.org/packages/ef/d4/cae11bf68c0f981e0413906c6dd03ae7fa864347ed5fac40021df1ef467c/cryptography-44.0.0-cp37-abi3-win32.whl", hash = "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053", size = 2757925 }, @@ -862,7 +863,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d0/c7/c656eb08fd22255d21bc3129625ed9cd5ee305f33752ef2278711b3fa98b/cryptography-44.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289", size = 3915417 }, { url = "https://files.pythonhosted.org/packages/ef/82/72403624f197af0db6bac4e58153bc9ac0e6020e57234115db9596eee85d/cryptography-44.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7", size = 4155160 }, { url = "https://files.pythonhosted.org/packages/a2/cd/2f3c440913d4329ade49b146d74f2e9766422e1732613f57097fea61f344/cryptography-44.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c", size = 3932331 }, - { url = "https://files.pythonhosted.org/packages/31/d9/90409720277f88eb3ab72f9a32bfa54acdd97e94225df699e7713e850bd4/cryptography-44.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9abcc2e083cbe8dde89124a47e5e53ec38751f0d7dfd36801008f316a127d7ba", size = 4165207 }, { url = "https://files.pythonhosted.org/packages/7f/df/8be88797f0a1cca6e255189a57bb49237402b1880d6e8721690c5603ac23/cryptography-44.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64", size = 4017372 }, { url = "https://files.pythonhosted.org/packages/af/36/5ccc376f025a834e72b8e52e18746b927f34e4520487098e283a719c205e/cryptography-44.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285", size = 4239657 }, { url = "https://files.pythonhosted.org/packages/46/b0/f4f7d0d0bcfbc8dd6296c1449be326d04217c57afb8b2594f017eed95533/cryptography-44.0.0-cp39-abi3-win32.whl", hash = "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417", size = 2758672 }, @@ -1556,7 +1556,7 @@ name = "importlib-resources" version = "6.4.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "zipp", marker = "python_full_version < '3.10'" }, + { name = "zipp", marker = "python_full_version < '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/98/be/f3e8c6081b684f176b761e6a2fef02a0be939740ed6f54109a2951d806f3/importlib_resources-6.4.5.tar.gz", hash = "sha256:980862a1d16c9e147a59603677fa2aa5fd82b87f223b6cb870695bcfce830065", size = 43372 } wheels = [ @@ -1756,7 +1756,8 @@ dependencies = [ { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, { name = "importlib-resources", marker = "python_full_version < '3.9'" }, { name = "litestar-htmx" }, - { name = "msgspec" }, + { name = "msgspec", version = "0.18.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "msgspec", version = "0.19.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "multidict" }, { name = "multipart" }, { name = "polyfactory" }, @@ -1798,7 +1799,7 @@ full = [ { name = "minijinja" }, { name = "opentelemetry-instrumentation-asgi" }, { name = "piccolo" }, - { name = "picologging" }, + { name = "picologging", marker = "python_full_version < '3.13'" }, { name = "prometheus-client" }, { name = "pydantic" }, { name = "pydantic-extra-types" }, @@ -1829,7 +1830,7 @@ piccolo = [ { name = "piccolo" }, ] picologging = [ - { name = "picologging" }, + { name = "picologging", marker = "python_full_version < '3.13'" }, ] prometheus = [ { name = "prometheus-client" }, @@ -1874,7 +1875,9 @@ dev = [ { name = "litestar", extra = ["full"] }, { name = "opentelemetry-sdk" }, { name = "psutil" }, - { name = "psycopg", extra = ["binary", "pool"] }, + { name = "psycopg", extra = ["binary"], marker = "python_full_version < '3.13'" }, + { name = "psycopg", extra = ["c"], marker = "python_full_version >= '3.13'" }, + { name = "psycopg", extra = ["pool"] }, { name = "psycopg2-binary" }, { name = "python-dotenv" }, { name = "starlette" }, @@ -1922,7 +1925,8 @@ test = [ requires-dist = [ { name = "advanced-alchemy", marker = "extra == 'sqlalchemy'", specifier = ">=0.2.2" }, { name = "annotated-types", marker = "extra == 'annotated-types'" }, - { name = "anyio", specifier = ">=3" }, + { name = "anyio", marker = "python_full_version < '3.9'", specifier = ">=3" }, + { name = "anyio", marker = "python_full_version >= '3.9'", specifier = ">=4" }, { name = "attrs", marker = "extra == 'attrs'" }, { name = "brotli", marker = "extra == 'brotli'" }, { name = "click" }, @@ -1938,20 +1942,23 @@ requires-dist = [ { name = "jinja2", marker = "extra == 'standard'" }, { name = "jsbeautifier", marker = "extra == 'cli'" }, { name = "jsbeautifier", marker = "extra == 'standard'" }, - { name = "litestar", extras = ["annotated-types", "attrs", "brotli", "cli", "cryptography", "jinja", "jwt", "mako", "minijinja", "opentelemetry", "piccolo", "picologging", "prometheus", "pydantic", "redis", "sqlalchemy", "standard", "structlog", "valkey"], marker = "extra == 'full'" }, + { name = "litestar", extras = ["annotated-types", "attrs", "brotli", "cli", "cryptography", "jinja", "jwt", "mako", "minijinja", "opentelemetry", "piccolo", "picologging", "prometheus", "pydantic", "redis", "sqlalchemy", "standard", "structlog", "valkey"], marker = "python_full_version < '3.13' and extra == 'full'" }, + { name = "litestar", extras = ["annotated-types", "attrs", "brotli", "cli", "cryptography", "jinja", "jwt", "mako", "minijinja", "opentelemetry", "piccolo", "prometheus", "pydantic", "redis", "sqlalchemy", "standard", "structlog", "valkey"], marker = "python_full_version >= '3.13' and extra == 'full'" }, { name = "litestar-htmx", specifier = ">=0.4.0" }, { name = "mako", marker = "extra == 'mako'", specifier = ">=1.2.4" }, { name = "minijinja", marker = "extra == 'minijinja'", specifier = ">=1.0.0" }, - { name = "msgspec", specifier = ">=0.18.2" }, + { name = "msgspec", marker = "python_full_version < '3.9'", specifier = ">=0.18.2,<0.19.0" }, + { name = "msgspec", marker = "python_full_version >= '3.9'", specifier = ">=0.19.0" }, { name = "multidict", specifier = ">=6.0.2" }, { name = "multipart", specifier = ">=1.2.0" }, { name = "opentelemetry-instrumentation-asgi", marker = "extra == 'opentelemetry'" }, { name = "piccolo", marker = "extra == 'piccolo'" }, - { name = "picologging", marker = "extra == 'picologging'" }, + { name = "picologging", marker = "python_full_version < '3.13' and extra == 'picologging'" }, { name = "polyfactory", specifier = ">=2.6.3" }, { name = "prometheus-client", marker = "extra == 'prometheus'" }, { name = "pydantic", marker = "extra == 'pydantic'" }, - { name = "pydantic-extra-types", marker = "extra == 'pydantic'" }, + { name = "pydantic-extra-types", marker = "python_full_version >= '3.9' and extra == 'pydantic'" }, + { name = "pydantic-extra-types", marker = "python_full_version < '3.9' and extra == 'pydantic'", specifier = "!=2.9.0" }, { name = "pyjwt", marker = "extra == 'jwt'", specifier = ">=2.9.0" }, { name = "pyyaml" }, { name = "redis", extras = ["hiredis"], marker = "extra == 'redis'", specifier = ">=4.4.4" }, @@ -1981,7 +1988,8 @@ dev = [ { name = "litestar", extras = ["full"] }, { name = "opentelemetry-sdk" }, { name = "psutil", specifier = ">=5.9.8" }, - { name = "psycopg", extras = ["pool", "binary"], specifier = ">=3.1.10,<3.2" }, + { name = "psycopg", extras = ["pool", "binary"], marker = "python_full_version < '3.13'", specifier = ">=3.1.10,<3.2" }, + { name = "psycopg", extras = ["pool", "c"], marker = "python_full_version >= '3.13'" }, { name = "psycopg2-binary" }, { name = "python-dotenv" }, { name = "starlette" }, @@ -2257,6 +2265,10 @@ wheels = [ name = "msgspec" version = "0.18.6" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9' and sys_platform != 'win32'", + "python_full_version < '3.9' and sys_platform == 'win32'", +] sdist = { url = "https://files.pythonhosted.org/packages/5e/fb/42b1865063fddb14dbcbb6e74e0a366ecf1ba371c4948664dde0b0e10f95/msgspec-0.18.6.tar.gz", hash = "sha256:a59fc3b4fcdb972d09138cb516dbde600c99d07c38fd9372a6ef500d2d031b4e", size = 216757 } wheels = [ { url = "https://files.pythonhosted.org/packages/49/54/34c2b70e0d42d876c04f6436c80777d786f25c7536830db5e4ec1aef8788/msgspec-0.18.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:77f30b0234eceeff0f651119b9821ce80949b4d667ad38f3bfed0d0ebf9d6d8f", size = 202537 }, @@ -2296,6 +2308,55 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cd/b2/283d010db6836db2fe059f7ee3c13823927229975ffbe1edcbeded85a556/msgspec-0.18.6-cp39-cp39-win_amd64.whl", hash = "sha256:b5c390b0b0b7da879520d4ae26044d74aeee5144f83087eb7842ba59c02bc090", size = 185801 }, ] +[[package]] +name = "msgspec" +version = "0.19.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.9' and python_full_version < '3.13' and sys_platform != 'win32'", + "python_full_version >= '3.13' and sys_platform != 'win32'", + "python_full_version >= '3.9' and python_full_version < '3.13' and sys_platform == 'win32'", + "python_full_version >= '3.13' and sys_platform == 'win32'", +] +sdist = { url = "https://files.pythonhosted.org/packages/cf/9b/95d8ce458462b8b71b8a70fa94563b2498b89933689f3a7b8911edfae3d7/msgspec-0.19.0.tar.gz", hash = "sha256:604037e7cd475345848116e89c553aa9a233259733ab51986ac924ab1b976f8e", size = 216934 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/40/817282b42f58399762267b30deb8ac011d8db373f8da0c212c85fbe62b8f/msgspec-0.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d8dd848ee7ca7c8153462557655570156c2be94e79acec3561cf379581343259", size = 190019 }, + { url = "https://files.pythonhosted.org/packages/92/99/bd7ed738c00f223a8119928661167a89124140792af18af513e6519b0d54/msgspec-0.19.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0553bbc77662e5708fe66aa75e7bd3e4b0f209709c48b299afd791d711a93c36", size = 183680 }, + { url = "https://files.pythonhosted.org/packages/e5/27/322badde18eb234e36d4a14122b89edd4e2973cdbc3da61ca7edf40a1ccd/msgspec-0.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe2c4bf29bf4e89790b3117470dea2c20b59932772483082c468b990d45fb947", size = 209334 }, + { url = "https://files.pythonhosted.org/packages/c6/65/080509c5774a1592b2779d902a70b5fe008532759927e011f068145a16cb/msgspec-0.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e87ecfa9795ee5214861eab8326b0e75475c2e68a384002aa135ea2a27d909", size = 211551 }, + { url = "https://files.pythonhosted.org/packages/6f/2e/1c23c6b4ca6f4285c30a39def1054e2bee281389e4b681b5e3711bd5a8c9/msgspec-0.19.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3c4ec642689da44618f68c90855a10edbc6ac3ff7c1d94395446c65a776e712a", size = 215099 }, + { url = "https://files.pythonhosted.org/packages/83/fe/95f9654518879f3359d1e76bc41189113aa9102452170ab7c9a9a4ee52f6/msgspec-0.19.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2719647625320b60e2d8af06b35f5b12d4f4d281db30a15a1df22adb2295f633", size = 218211 }, + { url = "https://files.pythonhosted.org/packages/79/f6/71ca7e87a1fb34dfe5efea8156c9ef59dd55613aeda2ca562f122cd22012/msgspec-0.19.0-cp310-cp310-win_amd64.whl", hash = "sha256:695b832d0091edd86eeb535cd39e45f3919f48d997685f7ac31acb15e0a2ed90", size = 186174 }, + { url = "https://files.pythonhosted.org/packages/24/d4/2ec2567ac30dab072cce3e91fb17803c52f0a37aab6b0c24375d2b20a581/msgspec-0.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa77046904db764b0462036bc63ef71f02b75b8f72e9c9dd4c447d6da1ed8f8e", size = 187939 }, + { url = "https://files.pythonhosted.org/packages/2b/c0/18226e4328897f4f19875cb62bb9259fe47e901eade9d9376ab5f251a929/msgspec-0.19.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:047cfa8675eb3bad68722cfe95c60e7afabf84d1bd8938979dd2b92e9e4a9551", size = 182202 }, + { url = "https://files.pythonhosted.org/packages/81/25/3a4b24d468203d8af90d1d351b77ea3cffb96b29492855cf83078f16bfe4/msgspec-0.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e78f46ff39a427e10b4a61614a2777ad69559cc8d603a7c05681f5a595ea98f7", size = 209029 }, + { url = "https://files.pythonhosted.org/packages/85/2e/db7e189b57901955239f7689b5dcd6ae9458637a9c66747326726c650523/msgspec-0.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c7adf191e4bd3be0e9231c3b6dc20cf1199ada2af523885efc2ed218eafd011", size = 210682 }, + { url = "https://files.pythonhosted.org/packages/03/97/7c8895c9074a97052d7e4a1cc1230b7b6e2ca2486714eb12c3f08bb9d284/msgspec-0.19.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f04cad4385e20be7c7176bb8ae3dca54a08e9756cfc97bcdb4f18560c3042063", size = 214003 }, + { url = "https://files.pythonhosted.org/packages/61/61/e892997bcaa289559b4d5869f066a8021b79f4bf8e955f831b095f47a4cd/msgspec-0.19.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:45c8fb410670b3b7eb884d44a75589377c341ec1392b778311acdbfa55187716", size = 216833 }, + { url = "https://files.pythonhosted.org/packages/ce/3d/71b2dffd3a1c743ffe13296ff701ee503feaebc3f04d0e75613b6563c374/msgspec-0.19.0-cp311-cp311-win_amd64.whl", hash = "sha256:70eaef4934b87193a27d802534dc466778ad8d536e296ae2f9334e182ac27b6c", size = 186184 }, + { url = "https://files.pythonhosted.org/packages/b2/5f/a70c24f075e3e7af2fae5414c7048b0e11389685b7f717bb55ba282a34a7/msgspec-0.19.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f98bd8962ad549c27d63845b50af3f53ec468b6318400c9f1adfe8b092d7b62f", size = 190485 }, + { url = "https://files.pythonhosted.org/packages/89/b0/1b9763938cfae12acf14b682fcf05c92855974d921a5a985ecc197d1c672/msgspec-0.19.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:43bbb237feab761b815ed9df43b266114203f53596f9b6e6f00ebd79d178cdf2", size = 183910 }, + { url = "https://files.pythonhosted.org/packages/87/81/0c8c93f0b92c97e326b279795f9c5b956c5a97af28ca0fbb9fd86c83737a/msgspec-0.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cfc033c02c3e0aec52b71710d7f84cb3ca5eb407ab2ad23d75631153fdb1f12", size = 210633 }, + { url = "https://files.pythonhosted.org/packages/d0/ef/c5422ce8af73928d194a6606f8ae36e93a52fd5e8df5abd366903a5ca8da/msgspec-0.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d911c442571605e17658ca2b416fd8579c5050ac9adc5e00c2cb3126c97f73bc", size = 213594 }, + { url = "https://files.pythonhosted.org/packages/19/2b/4137bc2ed45660444842d042be2cf5b18aa06efd2cda107cff18253b9653/msgspec-0.19.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:757b501fa57e24896cf40a831442b19a864f56d253679f34f260dcb002524a6c", size = 214053 }, + { url = "https://files.pythonhosted.org/packages/9d/e6/8ad51bdc806aac1dc501e8fe43f759f9ed7284043d722b53323ea421c360/msgspec-0.19.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5f0f65f29b45e2816d8bded36e6b837a4bf5fb60ec4bc3c625fa2c6da4124537", size = 219081 }, + { url = "https://files.pythonhosted.org/packages/b1/ef/27dd35a7049c9a4f4211c6cd6a8c9db0a50647546f003a5867827ec45391/msgspec-0.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:067f0de1c33cfa0b6a8206562efdf6be5985b988b53dd244a8e06f993f27c8c0", size = 187467 }, + { url = "https://files.pythonhosted.org/packages/3c/cb/2842c312bbe618d8fefc8b9cedce37f773cdc8fa453306546dba2c21fd98/msgspec-0.19.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f12d30dd6266557aaaf0aa0f9580a9a8fbeadfa83699c487713e355ec5f0bd86", size = 190498 }, + { url = "https://files.pythonhosted.org/packages/58/95/c40b01b93465e1a5f3b6c7d91b10fb574818163740cc3acbe722d1e0e7e4/msgspec-0.19.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82b2c42c1b9ebc89e822e7e13bbe9d17ede0c23c187469fdd9505afd5a481314", size = 183950 }, + { url = "https://files.pythonhosted.org/packages/e8/f0/5b764e066ce9aba4b70d1db8b087ea66098c7c27d59b9dd8a3532774d48f/msgspec-0.19.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19746b50be214a54239aab822964f2ac81e38b0055cca94808359d779338c10e", size = 210647 }, + { url = "https://files.pythonhosted.org/packages/9d/87/bc14f49bc95c4cb0dd0a8c56028a67c014ee7e6818ccdce74a4862af259b/msgspec-0.19.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60ef4bdb0ec8e4ad62e5a1f95230c08efb1f64f32e6e8dd2ced685bcc73858b5", size = 213563 }, + { url = "https://files.pythonhosted.org/packages/53/2f/2b1c2b056894fbaa975f68f81e3014bb447516a8b010f1bed3fb0e016ed7/msgspec-0.19.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac7f7c377c122b649f7545810c6cd1b47586e3aa3059126ce3516ac7ccc6a6a9", size = 213996 }, + { url = "https://files.pythonhosted.org/packages/aa/5a/4cd408d90d1417e8d2ce6a22b98a6853c1b4d7cb7669153e4424d60087f6/msgspec-0.19.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5bc1472223a643f5ffb5bf46ccdede7f9795078194f14edd69e3aab7020d327", size = 219087 }, + { url = "https://files.pythonhosted.org/packages/23/d8/f15b40611c2d5753d1abb0ca0da0c75348daf1252220e5dda2867bd81062/msgspec-0.19.0-cp313-cp313-win_amd64.whl", hash = "sha256:317050bc0f7739cb30d257ff09152ca309bf5a369854bbf1e57dffc310c1f20f", size = 187432 }, + { url = "https://files.pythonhosted.org/packages/ea/d0/323f867eaec1f2236ba30adf613777b1c97a7e8698e2e881656b21871fa4/msgspec-0.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15c1e86fff77184c20a2932cd9742bf33fe23125fa3fcf332df9ad2f7d483044", size = 189926 }, + { url = "https://files.pythonhosted.org/packages/a8/37/c3e1b39bdae90a7258d77959f5f5e36ad44b40e2be91cff83eea33c54d43/msgspec-0.19.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3b5541b2b3294e5ffabe31a09d604e23a88533ace36ac288fa32a420aa38d229", size = 183873 }, + { url = "https://files.pythonhosted.org/packages/cb/a2/48f2c15c7644668e51f4dce99d5f709bd55314e47acb02e90682f5880f35/msgspec-0.19.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f5c043ace7962ef188746e83b99faaa9e3e699ab857ca3f367b309c8e2c6b12", size = 209272 }, + { url = "https://files.pythonhosted.org/packages/25/3c/aa339cf08b990c3f07e67b229a3a8aa31bf129ed974b35e5daa0df7d9d56/msgspec-0.19.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca06aa08e39bf57e39a258e1996474f84d0dd8130d486c00bec26d797b8c5446", size = 211396 }, + { url = "https://files.pythonhosted.org/packages/c7/00/c7fb9d524327c558b2803973cc3f988c5100a1708879970a9e377bdf6f4f/msgspec-0.19.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e695dad6897896e9384cf5e2687d9ae9feaef50e802f93602d35458e20d1fb19", size = 215002 }, + { url = "https://files.pythonhosted.org/packages/3f/bf/d9f9fff026c1248cde84a5ce62b3742e8a63a3c4e811f99f00c8babf7615/msgspec-0.19.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3be5c02e1fee57b54130316a08fe40cca53af92999a302a6054cd451700ea7db", size = 218132 }, + { url = "https://files.pythonhosted.org/packages/00/03/b92011210f79794958167a3a3ea64a71135d9a2034cfb7597b545a42606d/msgspec-0.19.0-cp39-cp39-win_amd64.whl", hash = "sha256:0684573a821be3c749912acf5848cce78af4298345cb2d7a8b8948a0a5a27cfe", size = 186301 }, +] + [[package]] name = "multidict" version = "6.1.0" @@ -2745,7 +2806,10 @@ wheels = [ [package.optional-dependencies] binary = [ - { name = "psycopg-binary", marker = "implementation_name != 'pypy'" }, + { name = "psycopg-binary", marker = "python_full_version < '3.13' and implementation_name != 'pypy'" }, +] +c = [ + { name = "psycopg-c", marker = "python_full_version >= '3.13' and implementation_name != 'pypy'" }, ] pool = [ { name = "psycopg-pool" }, @@ -2811,6 +2875,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/01/10/01ec61e06f6bb2305ab66f05f0501431ce5edbb61700e1cd5fa73cf05884/psycopg_binary-3.1.20-cp39-cp39-win_amd64.whl", hash = "sha256:47dd369cb4b263d29aed12ee23b37c03e58bfe656843692d109896c258c554b0", size = 2896197 }, ] +[[package]] +name = "psycopg-c" +version = "3.1.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/49/6960b5e5fc82ab4dbde845bbd430d9f9e049847178b3bbe7f1c49c145667/psycopg_c-3.1.20.tar.gz", hash = "sha256:a8dadb012fce8918b0c35d9e5be3d6ba4495067117ee45fa49644e46be3c43c8", size = 562110 } + [[package]] name = "psycopg-pool" version = "3.2.4" From 71740045f64a3bb81ca2cb7392865b79fea27c91 Mon Sep 17 00:00:00 2001 From: Cody Fincher Date: Mon, 30 Dec 2024 01:54:21 +0000 Subject: [PATCH 04/32] fix(sqlalchemy): updates to documentation based on upstream changes --- .../sqlalchemy_declarative_models.py | 11 +++++---- litestar/contrib/sqlalchemy/base.py | 16 ++++++++----- .../test_sqlalchemy_examples.py | 24 ++++++++++++++++--- tests/unit/test_plugins/test_sqlalchemy.py | 20 +++++++++------- 4 files changed, 48 insertions(+), 23 deletions(-) diff --git a/docs/examples/contrib/sqlalchemy/sqlalchemy_declarative_models.py b/docs/examples/contrib/sqlalchemy/sqlalchemy_declarative_models.py index 2a4f7e302c..251f98b63f 100644 --- a/docs/examples/contrib/sqlalchemy/sqlalchemy_declarative_models.py +++ b/docs/examples/contrib/sqlalchemy/sqlalchemy_declarative_models.py @@ -10,13 +10,13 @@ from sqlalchemy.orm import Mapped, mapped_column, relationship from litestar import Litestar, get -from litestar.contrib.sqlalchemy.base import UUIDAuditBase, UUIDBase -from litestar.contrib.sqlalchemy.plugins import AsyncSessionConfig, SQLAlchemyAsyncConfig, SQLAlchemyPlugin +from litestar.plugins.sqlalchemy import AsyncSessionConfig, SQLAlchemyAsyncConfig, SQLAlchemyPlugin, base # The SQLAlchemy base includes a declarative model for you to use in your models. # The `UUIDBase` class includes a `UUID` based primary key (`id`) -class Author(UUIDBase): +class Author(base.UUIDBase): + __tablename__ = "author" name: Mapped[str] dob: Mapped[date] books: Mapped[List[Book]] = relationship(back_populates="author", lazy="selectin") @@ -25,7 +25,8 @@ class Author(UUIDBase): # The `UUIDAuditBase` class includes the same UUID` based primary key (`id`) and 2 # additional columns: `created_at` and `updated_at`. `created_at` is a timestamp of when the # record created, and `updated_at` is the last time the record was modified. -class Book(UUIDAuditBase): +class Book(base.UUIDAuditBase): + __tablename__ = "book" title: Mapped[str] author_id: Mapped[UUID] = mapped_column(ForeignKey("author.id")) author: Mapped[Author] = relationship(lazy="joined", innerjoin=True, viewonly=True) @@ -37,7 +38,7 @@ class Book(UUIDAuditBase): ) # Create 'async_session' dependency. -async def on_startup() -> None: +async def on_startup(app: Litestar) -> None: """Adds some dummy data if no data is present.""" async with sqlalchemy_config.get_session() as session: statement = select(func.count()).select_from(Author) diff --git a/litestar/contrib/sqlalchemy/base.py b/litestar/contrib/sqlalchemy/base.py index 4cb13e0d7e..1a1e147eb1 100644 --- a/litestar/contrib/sqlalchemy/base.py +++ b/litestar/contrib/sqlalchemy/base.py @@ -43,18 +43,20 @@ def __getattr__(attr_name: str) -> object: value = globals()[attr_name] = locals()[attr_name] # pyright: ignore[reportUnknownVariableType] return value # pyright: ignore[reportUnknownVariableType] from advanced_alchemy.base import ( # pyright: ignore[reportMissingImports] - AuditColumns, BigIntAuditBase, BigIntBase, - BigIntPrimaryKey, CommonTableAttributes, ModelProtocol, UUIDAuditBase, UUIDBase, - UUIDPrimaryKey, create_registry, orm_registry, ) + from advanced_alchemy.mixins import ( # pyright: ignore[reportMissingImports] + AuditColumns, + BigIntPrimaryKey, + UUIDPrimaryKey, + ) warn_deprecation( deprecated_name=f"litestar.contrib.sqlalchemy.base.{attr_name}", @@ -78,15 +80,17 @@ def __getattr__(attr_name: str) -> object: from advanced_alchemy.base import touch_updated_timestamp # type: ignore[no-redef,attr-defined] from advanced_alchemy.base import ( # pyright: ignore[reportMissingImports] - AuditColumns, BigIntAuditBase, BigIntBase, - BigIntPrimaryKey, CommonTableAttributes, ModelProtocol, UUIDAuditBase, UUIDBase, - UUIDPrimaryKey, create_registry, orm_registry, ) + from advanced_alchemy.mixins import ( # pyright: ignore[reportMissingImports] + AuditColumns, + BigIntPrimaryKey, + UUIDPrimaryKey, + ) diff --git a/tests/examples/test_contrib/test_sqlalchemy/test_sqlalchemy_examples.py b/tests/examples/test_contrib/test_sqlalchemy/test_sqlalchemy_examples.py index 0aa531bd33..55b48776c9 100644 --- a/tests/examples/test_contrib/test_sqlalchemy/test_sqlalchemy_examples.py +++ b/tests/examples/test_contrib/test_sqlalchemy/test_sqlalchemy_examples.py @@ -1,14 +1,32 @@ +from pathlib import Path + import pytest +from pytest import MonkeyPatch +from sqlalchemy.ext.asyncio import create_async_engine +from sqlalchemy.pool import NullPool +from litestar.plugins.sqlalchemy import AsyncSessionConfig, SQLAlchemyAsyncConfig from litestar.testing import TestClient pytestmark = pytest.mark.xdist_group("sqlalchemy_examples") -def test_sqlalchemy_declarative_models() -> None: - from docs.examples.contrib.sqlalchemy.sqlalchemy_declarative_models import app +async def test_sqlalchemy_declarative_models(tmp_path: Path, monkeypatch: MonkeyPatch) -> None: + engine = create_async_engine("sqlite+aiosqlite:///test.sqlite", poolclass=NullPool) + + session_config = AsyncSessionConfig(expire_on_commit=False) + sqlalchemy_config = SQLAlchemyAsyncConfig( + session_config=session_config, + create_all=True, + engine_instance=engine, + ) # Create 'async_session' dependency. + from docs.examples.contrib.sqlalchemy import sqlalchemy_declarative_models - with TestClient(app) as client: + monkeypatch.setattr(sqlalchemy_declarative_models, "sqlalchemy_config", sqlalchemy_config) + async with engine.begin() as connection: + await connection.run_sync(sqlalchemy_declarative_models.Author.metadata.create_all) + await connection.commit() + with TestClient(sqlalchemy_declarative_models.app) as client: response = client.get("/authors") assert response.status_code == 200 assert len(response.json()) > 0 diff --git a/tests/unit/test_plugins/test_sqlalchemy.py b/tests/unit/test_plugins/test_sqlalchemy.py index 69512e1a9b..8100f8377d 100644 --- a/tests/unit/test_plugins/test_sqlalchemy.py +++ b/tests/unit/test_plugins/test_sqlalchemy.py @@ -1,3 +1,4 @@ +import pytest from advanced_alchemy.extensions import litestar as sa_litestar from advanced_alchemy.extensions.litestar import base as sa_base from advanced_alchemy.extensions.litestar import exceptions as sa_exceptions @@ -38,12 +39,13 @@ def test_re_exports() -> None: assert sqlalchemy.SyncSessionConfig is sa_litestar.SyncSessionConfig # deprecated, to be removed later - assert sqlalchemy.AuditColumns is sa_base.AuditColumns - assert sqlalchemy.BigIntAuditBase is sa_base.BigIntAuditBase - assert sqlalchemy.BigIntBase is sa_base.BigIntBase - assert sqlalchemy.BigIntPrimaryKey is sa_base.BigIntPrimaryKey - assert sqlalchemy.CommonTableAttributes is sa_base.CommonTableAttributes - assert sqlalchemy.UUIDAuditBase is sa_base.UUIDAuditBase - assert sqlalchemy.UUIDBase is sa_base.UUIDBase - assert sqlalchemy.UUIDPrimaryKey is sa_base.UUIDPrimaryKey - assert sqlalchemy.orm_registry is sa_base.orm_registry + with pytest.warns(DeprecationWarning): + assert sqlalchemy.AuditColumns is sa_base.AuditColumns + assert sqlalchemy.BigIntAuditBase is sa_base.BigIntAuditBase + assert sqlalchemy.BigIntBase is sa_base.BigIntBase + assert sqlalchemy.BigIntPrimaryKey is sa_base.BigIntPrimaryKey + assert sqlalchemy.CommonTableAttributes is sa_base.CommonTableAttributes + assert sqlalchemy.UUIDAuditBase is sa_base.UUIDAuditBase + assert sqlalchemy.UUIDBase is sa_base.UUIDBase + assert sqlalchemy.UUIDPrimaryKey is sa_base.UUIDPrimaryKey + assert sqlalchemy.orm_registry is sa_base.orm_registry From f870e2cfb00ed0f3e0af6e63cdcfbd667c75de35 Mon Sep 17 00:00:00 2001 From: Cody Fincher Date: Mon, 30 Dec 2024 01:58:09 +0000 Subject: [PATCH 05/32] chore: bump AA version --- uv.lock | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/uv.lock b/uv.lock index 20157cb2e5..9dcc5cc9c1 100644 --- a/uv.lock +++ b/uv.lock @@ -21,7 +21,7 @@ wheels = [ [[package]] name = "advanced-alchemy" -version = "0.25.0" +version = "0.26.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "alembic" }, @@ -30,9 +30,9 @@ dependencies = [ { name = "sqlalchemy" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cc/61/5a9049b3300306b21344203243cd8103e8cc9d96747c76d7b9480f994965/advanced_alchemy-0.25.0.tar.gz", hash = "sha256:42cd1249e4f06568690af4183519d50a058cffb53104dda50b9e7ad1fc8b5844", size = 934683 } +sdist = { url = "https://files.pythonhosted.org/packages/3e/94/8783c58213448ae3fe44615e3efd2eb5bfc3d90a8fe47d29f9e6164681f2/advanced_alchemy-0.26.2.tar.gz", hash = "sha256:b56a9c42b7c1b1ab322cccb39b5fd0601232850b10191337f0504debc71735d2", size = 983000 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/2b/0d4705ed21912c508b5be22533003f3cc0b640e267210ce3d4612de58594/advanced_alchemy-0.25.0-py3-none-any.whl", hash = "sha256:c2783278fbe5a75c3eeba2ae041e9985e17ecaafb5d388ec9dd6d5765218428b", size = 142942 }, + { url = "https://files.pythonhosted.org/packages/9e/ef/35219f6be810e636fbe26e05af6c767d02de825075b7e633f49cf886b355/advanced_alchemy-0.26.2-py3-none-any.whl", hash = "sha256:1f9b1207e757076e13a41782e76ac32f50ab5851a88d40f27321005cd46b6b94", size = 147848 }, ] [[package]] @@ -851,7 +851,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/98/65/13d9e76ca19b0ba5603d71ac8424b5694415b348e719db277b5edc985ff5/cryptography-44.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb", size = 3915420 }, { url = "https://files.pythonhosted.org/packages/b1/07/40fe09ce96b91fc9276a9ad272832ead0fddedcba87f1190372af8e3039c/cryptography-44.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b", size = 4154498 }, { url = "https://files.pythonhosted.org/packages/75/ea/af65619c800ec0a7e4034207aec543acdf248d9bffba0533342d1bd435e1/cryptography-44.0.0-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543", size = 3932569 }, - { url = "https://files.pythonhosted.org/packages/4e/d5/9cc182bf24c86f542129565976c21301d4ac397e74bf5a16e48241aab8a6/cryptography-44.0.0-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:60eb32934076fa07e4316b7b2742fa52cbb190b42c2df2863dbc4230a0a9b385", size = 4164756 }, { url = "https://files.pythonhosted.org/packages/c7/af/d1deb0c04d59612e3d5e54203159e284d3e7a6921e565bb0eeb6269bdd8a/cryptography-44.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e", size = 4016721 }, { url = "https://files.pythonhosted.org/packages/bd/69/7ca326c55698d0688db867795134bdfac87136b80ef373aaa42b225d6dd5/cryptography-44.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e", size = 4240915 }, { url = "https://files.pythonhosted.org/packages/ef/d4/cae11bf68c0f981e0413906c6dd03ae7fa864347ed5fac40021df1ef467c/cryptography-44.0.0-cp37-abi3-win32.whl", hash = "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053", size = 2757925 }, @@ -862,7 +861,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d0/c7/c656eb08fd22255d21bc3129625ed9cd5ee305f33752ef2278711b3fa98b/cryptography-44.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289", size = 3915417 }, { url = "https://files.pythonhosted.org/packages/ef/82/72403624f197af0db6bac4e58153bc9ac0e6020e57234115db9596eee85d/cryptography-44.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7", size = 4155160 }, { url = "https://files.pythonhosted.org/packages/a2/cd/2f3c440913d4329ade49b146d74f2e9766422e1732613f57097fea61f344/cryptography-44.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c", size = 3932331 }, - { url = "https://files.pythonhosted.org/packages/31/d9/90409720277f88eb3ab72f9a32bfa54acdd97e94225df699e7713e850bd4/cryptography-44.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9abcc2e083cbe8dde89124a47e5e53ec38751f0d7dfd36801008f316a127d7ba", size = 4165207 }, { url = "https://files.pythonhosted.org/packages/7f/df/8be88797f0a1cca6e255189a57bb49237402b1880d6e8721690c5603ac23/cryptography-44.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64", size = 4017372 }, { url = "https://files.pythonhosted.org/packages/af/36/5ccc376f025a834e72b8e52e18746b927f34e4520487098e283a719c205e/cryptography-44.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285", size = 4239657 }, { url = "https://files.pythonhosted.org/packages/46/b0/f4f7d0d0bcfbc8dd6296c1449be326d04217c57afb8b2594f017eed95533/cryptography-44.0.0-cp39-abi3-win32.whl", hash = "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417", size = 2758672 }, From 99e8b746d7710c19f156cb98a1eccc35a784c37e Mon Sep 17 00:00:00 2001 From: Cody Fincher Date: Mon, 30 Dec 2024 02:02:34 +0000 Subject: [PATCH 06/32] fix(picologging): updates to handle unsupported environments (python 3.13, etc.) --- litestar/logging/config.py | 7 +- litestar/logging/picologging.py | 12 +- tests/helpers.py | 15 +- .../unit/test_logging/test_logging_config.py | 215 ++++++++++++++---- 4 files changed, 189 insertions(+), 60 deletions(-) diff --git a/litestar/logging/config.py b/litestar/logging/config.py index 19615a229f..1edf357188 100644 --- a/litestar/logging/config.py +++ b/litestar/logging/config.py @@ -292,13 +292,16 @@ def configure(self) -> GetLogger: if self.logging_module == "picologging": try: - from picologging import config, getLogger + from picologging import ( # pyright: ignore[reportMissingImports,reportGeneralTypeIssues] + config, # pyright: ignore[reportMissingImports,reportGeneralTypeIssues] + getLogger, # pyright: ignore[reportMissingImports,reportGeneralTypeIssues] + ) except ImportError as e: raise MissingDependencyException("picologging") from e excluded_fields.add("incremental") else: - from logging import config, getLogger # type: ignore[no-redef, assignment] + from logging import config, getLogger # type: ignore[no-redef,assignment,unused-ignore] values = { _field.name: getattr(self, _field.name) diff --git a/litestar/logging/picologging.py b/litestar/logging/picologging.py index 2cd599f463..e07c074283 100644 --- a/litestar/logging/picologging.py +++ b/litestar/logging/picologging.py @@ -11,15 +11,15 @@ try: - import picologging # noqa: F401 + import picologging # noqa: F401 # pyright: ignore[reportMissingImports] except ImportError as e: raise MissingDependencyException("picologging") from e -from picologging import StreamHandler -from picologging.handlers import QueueHandler, QueueListener +from picologging import StreamHandler # pyright: ignore[reportMissingImports] +from picologging.handlers import QueueHandler, QueueListener # pyright: ignore[reportMissingImports] -class QueueListenerHandler(QueueHandler): +class QueueListenerHandler(QueueHandler): # type: ignore[misc,unused-ignore] """Configure queue listener and handler to support non-blocking logging configuration.""" def __init__(self, handlers: list[Any] | None = None) -> None: @@ -32,8 +32,8 @@ def __init__(self, handlers: list[Any] | None = None) -> None: - Requires ``picologging`` to be installed. """ super().__init__(Queue(-1)) - handlers = resolve_handlers(handlers) if handlers else [StreamHandler()] - self.listener = QueueListener(self.queue, *handlers) + handlers = resolve_handlers(handlers) if handlers else [StreamHandler()] # pyright: ignore[reportGeneralTypeIssues] + self.listener = QueueListener(self.queue, *handlers) # pyright: ignore[reportGeneralTypeIssues] self.listener.start() atexit.register(self.listener.stop) diff --git a/tests/helpers.py b/tests/helpers.py index b541ed28ec..353a592eb5 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -1,14 +1,16 @@ from __future__ import annotations import atexit +import importlib.util import inspect import logging import random import sys from contextlib import AbstractContextManager, contextmanager +from pathlib import Path from typing import Any, AsyncContextManager, Awaitable, ContextManager, Generator, TypeVar, cast, overload -import picologging +import pytest from _pytest.logging import LogCaptureHandler, _LiveLoggingNullHandler from litestar._openapi.schema_generation import SchemaCreator @@ -90,9 +92,9 @@ def cleanup_logging_impl() -> Generator: # Don't interfere with PyTest handler config if not isinstance(std_handler, (_LiveLoggingNullHandler, LogCaptureHandler)): std_root_logger.removeHandler(std_handler) - + picologging = pytest.importorskip("picologging") # Reset root logger (`picologging` module) - pico_root_logger: picologging.Logger = picologging.getLogger() + pico_root_logger: picologging.Logger = picologging.getLogger() # type: ignore[name-defined,unused-ignore] # pyright: ignore[reportPrivateUsage,reportGeneralTypeIssues,reportAssignmentType,reportInvalidTypeForm] for pico_handler in pico_root_logger.handlers: pico_root_logger.removeHandler(pico_handler) @@ -111,3 +113,10 @@ def cleanup_logging_impl() -> Generator: def not_none(val: T | None) -> T: assert val is not None return val + + +def purge_module(module_names: list[str], path: str | Path) -> None: + for name in module_names: + if name in sys.modules: + del sys.modules[name] + Path(importlib.util.cache_from_source(path)).unlink(missing_ok=True) # type: ignore[arg-type] diff --git a/tests/unit/test_logging/test_logging_config.py b/tests/unit/test_logging/test_logging_config.py index c4b73d8994..3423184efc 100644 --- a/tests/unit/test_logging/test_logging_config.py +++ b/tests/unit/test_logging/test_logging_config.py @@ -1,14 +1,13 @@ +import importlib import logging import sys import time from importlib.util import find_spec from logging.handlers import QueueHandler from queue import Queue -from types import ModuleType -from typing import TYPE_CHECKING, Any, Dict, Generator, Optional +from typing import TYPE_CHECKING, Any, Dict, Generator, Optional, Union, cast from unittest.mock import patch -import picologging import pytest from _pytest.logging import LogCaptureHandler, _LiveLoggingNullHandler @@ -21,9 +20,6 @@ default_handlers, default_picologging_handlers, ) -from litestar.logging.picologging import QueueListenerHandler as PicologgingQueueListenerHandler -from litestar.logging.standard import LoggingQueueListener -from litestar.logging.standard import QueueListenerHandler as StandardQueueListenerHandler from litestar.status_codes import HTTP_200_OK from litestar.testing import create_test_client from tests.helpers import cleanup_logging_impl @@ -34,6 +30,7 @@ @pytest.fixture(autouse=True) def cleanup_logging() -> Generator: + _ = pytest.importorskip("picologging") with cleanup_logging_impl(): yield @@ -123,23 +120,54 @@ def test_dictconfig_on_startup(logging_module: str, dict_config_not_called: str) @pytest.mark.parametrize( - "logging_module, expected_handler_class, expected_listener_class", + "logging_module_str, expected_handler_class_str, expected_listener_class_str", [ [ - logging, - QueueHandler if sys.version_info >= (3, 12, 0) else StandardQueueListenerHandler, - LoggingQueueListener, + "logging", + "logging.handlers.QueueHandler" + if sys.version_info >= (3, 12, 0) + else "litestar.logging.standard.QueueListenerHandler", + "litestar.logging.standard.LoggingQueueListener", ], [ - picologging, - PicologgingQueueListenerHandler, - picologging.handlers.QueueListener, # pyright: ignore[reportGeneralTypeIssues] + "picologging", + "litestar.logging.picologging.QueueListenerHandler", + "picologging.handlers.QueueListener", # pyright: ignore[reportGeneralTypeIssues,reportAttributeAccessIssue] ], ], ) def test_default_queue_listener_handler( - logging_module: ModuleType, expected_handler_class: Any, expected_listener_class: Any, capsys: "CaptureFixture[str]" + logging_module_str: str, + expected_handler_class_str: Union[str, Any], + expected_listener_class_str: str, + capsys: "CaptureFixture[str]", ) -> None: + logging_module = importlib.import_module(logging_module_str) + if expected_handler_class_str == "litestar.logging.standard.QueueListenerHandler": + from litestar.logging.standard import QueueListenerHandler + + expected_handler_class = QueueListenerHandler + elif expected_handler_class_str == "litestar.logging.picologging.QueueListenerHandler": + from litestar.logging.picologging import QueueListenerHandler + + expected_handler_class = QueueListenerHandler + elif expected_handler_class_str == "logging.handlers.QueueHandler": + from logging.handlers import QueueHandler as QueueListenerHandler + + expected_handler_class = QueueListenerHandler + else: + expected_handler_class = importlib.import_module(expected_handler_class_str) + if expected_listener_class_str == "litestar.logging.standard.LoggingQueueListener": + from litestar.logging.standard import LoggingQueueListener + + expected_listener_class = LoggingQueueListener + elif expected_listener_class_str == "picologging.handlers.QueueListener": + from picologging.handlers import QueueListener # pyright: ignore[reportMissingImports] + + expected_listener_class = QueueListener + else: + expected_listener_class = importlib.import_module(expected_listener_class_str) + def wait_log_queue(queue: Any, sleep_time: float = 0.1, max_retries: int = 5) -> None: retry = 0 while queue.qsize() > 0 and retry < max_retries: @@ -168,7 +196,7 @@ def assert_log(queue: Any, expected: str, count: Optional[int] = None) -> None: logger = get_logger("test_logger") assert type(logger) is logging_module.Logger - handler = logger.handlers[0] # pyright: ignore[reportGeneralTypeIssues] + handler = logger.handlers[0] # pyright: ignore[reportGeneralTypeIssues,reportAttributeAccessIssue] assert type(handler) is expected_handler_class assert type(handler.queue) is Queue @@ -193,14 +221,34 @@ def test_get_logger_without_logging_config() -> None: @pytest.mark.parametrize( - "logging_module, expected_handler_class", + "logging_module_str, expected_handler_class_str", [ - [logging, QueueHandler if sys.version_info >= (3, 12, 0) else StandardQueueListenerHandler], - [picologging, PicologgingQueueListenerHandler], + [ + "logging", + "logging.handlers.QueueHandler" + if sys.version_info >= (3, 12, 0) + else "litestar.logging.standard.QueueListenerHandler", + ], + ["picologging", "litestar.logging.picologging.QueueListenerHandler"], ], ) -def test_default_loggers(logging_module: ModuleType, expected_handler_class: Any) -> None: - with create_test_client(logging_config=LoggingConfig(logging_module=logging_module.__name__)) as client: +def test_default_loggers(logging_module_str: str, expected_handler_class_str: str) -> None: + logging_module = importlib.import_module(logging_module_str) + if expected_handler_class_str == "litestar.logging.standard.QueueListenerHandler": + from litestar.logging.standard import QueueListenerHandler + + expected_handler_class = QueueListenerHandler + elif expected_handler_class_str == "litestar.logging.picologging.QueueListenerHandler": + from litestar.logging.picologging import QueueListenerHandler + + expected_handler_class = QueueListenerHandler + elif expected_handler_class_str == "logging.handlers.QueueHandler": + from logging.handlers import QueueHandler as QueueListenerHandler + + expected_handler_class = QueueListenerHandler + else: + expected_handler_class = importlib.import_module(expected_handler_class_str) + with create_test_client(logging_config=LoggingConfig(logging_module=logging_module_str)) as client: root_logger = client.app.get_logger() assert isinstance(root_logger, logging_module.Logger) assert root_logger.name == "root" @@ -216,28 +264,50 @@ def test_default_loggers(logging_module: ModuleType, expected_handler_class: Any @pytest.mark.parametrize( - "logging_module, expected_handler_class", + "logging_module_str, expected_handler_class_str", [ - ["logging", QueueHandler if sys.version_info >= (3, 12, 0) else StandardQueueListenerHandler], - ["picologging", PicologgingQueueListenerHandler], + [ + "logging", + "logging.handlers.QueueHandler" + if sys.version_info >= (3, 12, 0) + else "litestar.logging.standard.QueueListenerHandler", + ], + ["picologging", "litestar.logging.picologging.QueueListenerHandler"], ], ) -def test_connection_logger(logging_module: str, expected_handler_class: Any) -> None: +def test_connection_logger(logging_module_str: str, expected_handler_class_str: str) -> None: + logging_module = importlib.import_module(logging_module_str) + if expected_handler_class_str == "litestar.logging.standard.QueueListenerHandler": + from litestar.logging.standard import QueueListenerHandler + + expected_handler_class = QueueListenerHandler + elif expected_handler_class_str == "litestar.logging.picologging.QueueListenerHandler": + from litestar.logging.picologging import QueueListenerHandler + + expected_handler_class = QueueListenerHandler + elif expected_handler_class_str == "logging.handlers.QueueHandler": + from logging.handlers import QueueHandler as QueueListenerHandler + + expected_handler_class = QueueListenerHandler + else: + expected_handler_class = importlib.import_module(expected_handler_class_str) + @get("/") def handler(request: Request) -> Dict[str, bool]: return {"isinstance": isinstance(request.logger.handlers[0], expected_handler_class)} # type: ignore[attr-defined] with create_test_client( route_handlers=[handler], - logging_config=LoggingConfig(logging_module=logging_module), + logging_config=LoggingConfig(logging_module=logging_module.__name__), ) as client: response = client.get("/") assert response.status_code == HTTP_200_OK assert response.json()["isinstance"] -@pytest.mark.parametrize("logging_module", [logging, picologging, None]) -def test_validation(logging_module: Optional[ModuleType]) -> None: +@pytest.mark.parametrize("logging_module_str", ["logging", "picologging", None]) +def test_validation(logging_module_str: Optional[str]) -> None: + logging_module = importlib.import_module(logging_module_str) if logging_module_str else None if logging_module is None: logging_config = LoggingConfig( formatters={}, @@ -267,32 +337,54 @@ def test_validation(logging_module: Optional[ModuleType]) -> None: @pytest.mark.parametrize( - "logging_module, expected_handler_class", + "logging_module_str, expected_handler_class_str", [ - [logging, QueueHandler if sys.version_info >= (3, 12, 0) else StandardQueueListenerHandler], - [picologging, PicologgingQueueListenerHandler], + [ + "logging", + "logging.handlers.QueueHandler" + if sys.version_info >= (3, 12, 0) + else "litestar.logging.standard.QueueListenerHandler", + ], + ["picologging", "litestar.logging.picologging.QueueListenerHandler"], ], ) -def test_root_logger(logging_module: ModuleType, expected_handler_class: Any) -> None: +def test_root_logger(logging_module_str: str, expected_handler_class_str: str) -> None: + logging_module = importlib.import_module(logging_module_str) + if expected_handler_class_str == "litestar.logging.standard.QueueListenerHandler": + from litestar.logging.standard import QueueListenerHandler + + expected_handler_class = QueueListenerHandler + elif expected_handler_class_str == "litestar.logging.picologging.QueueListenerHandler": + from litestar.logging.picologging import QueueListenerHandler + + expected_handler_class = QueueListenerHandler + elif expected_handler_class_str == "logging.handlers.QueueHandler": + from logging.handlers import QueueHandler as QueueListenerHandler + + expected_handler_class = QueueListenerHandler + else: + expected_handler_class = importlib.import_module(expected_handler_class_str) + logging_config = LoggingConfig(logging_module=logging_module.__name__) get_logger = logging_config.configure() root_logger = get_logger() assert root_logger.name == "root" # type: ignore[attr-defined] assert isinstance(root_logger, logging_module.Logger) - root_logger_handler = root_logger.handlers[0] # pyright: ignore[reportGeneralTypeIssues] + root_logger_handler = root_logger.handlers[0] # pyright: ignore[reportGeneralTypeIssues,reportAttributeAccessIssue] assert root_logger_handler.name == "queue_listener" - assert isinstance(root_logger_handler, expected_handler_class) + assert isinstance(root_logger_handler, cast("Any", expected_handler_class)) -@pytest.mark.parametrize("logging_module", [logging, picologging]) -def test_root_logger_no_config(logging_module: ModuleType) -> None: - logging_config = LoggingConfig(logging_module=logging_module.__name__, configure_root_logger=False) +@pytest.mark.parametrize("logging_module_str", ["logging", "picologging"]) +def test_root_logger_no_config(logging_module_str: str) -> None: + logging_module = importlib.import_module(logging_module_str) + logging_config = LoggingConfig(logging_module=logging_module_str, configure_root_logger=False) get_logger = logging_config.configure() root_logger = get_logger() assert isinstance(root_logger, logging_module.Logger) - handlers = root_logger.handlers # pyright: ignore[reportGeneralTypeIssues] + handlers = root_logger.handlers # pyright: ignore[reportGeneralTypeIssues,reportAttributeAccessIssue] if logging_module == logging: # pytest automatically configures some handlers for handler in handlers: @@ -302,20 +394,44 @@ def test_root_logger_no_config(logging_module: ModuleType) -> None: @pytest.mark.parametrize( - "logging_module, configure_root_logger, expected_root_logger_handler_class", + "logging_module_str, configure_root_logger, expected_root_logger_handler_class_str", [ - [logging, True, QueueHandler if sys.version_info >= (3, 12, 0) else StandardQueueListenerHandler], - [logging, False, None], - [picologging, True, PicologgingQueueListenerHandler], - [picologging, False, None], + [ + "logging", + True, + "logging.handlers.QueueHandler" + if sys.version_info >= (3, 12, 0) + else "litestar.logging.standard.QueueListenerHandler", + ], + ["logging", False, None], + ["picologging", True, "litestar.logging.picologging.QueueListenerHandler"], + ["picologging", False, None], ], ) def test_customizing_handler( - logging_module: ModuleType, + logging_module_str: str, configure_root_logger: bool, - expected_root_logger_handler_class: Any, + expected_root_logger_handler_class_str: "Optional[str]", capsys: "CaptureFixture[str]", ) -> None: + logging_module = importlib.import_module(logging_module_str) + if expected_root_logger_handler_class_str is None: + expected_root_logger_handler_class = None + elif expected_root_logger_handler_class_str == "litestar.logging.standard.QueueListenerHandler": + from litestar.logging.standard import QueueListenerHandler + + expected_root_logger_handler_class = QueueListenerHandler + elif expected_root_logger_handler_class_str == "litestar.logging.picologging.QueueListenerHandler": + from litestar.logging.picologging import QueueListenerHandler + + expected_root_logger_handler_class = QueueListenerHandler + elif expected_root_logger_handler_class_str == "logging.handlers.QueueHandler": + from logging.handlers import QueueHandler as QueueListenerHandler + + expected_root_logger_handler_class = QueueListenerHandler + else: + expected_root_logger_handler_class = importlib.import_module(expected_root_logger_handler_class_str) + log_format = "%(levelname)s :: %(name)s :: %(message)s" logging_config = LoggingConfig( @@ -348,7 +464,7 @@ def test_customizing_handler( # picologging seems to be broken, cannot make it log on stdout? # https://github.com/microsoft/picologging/issues/205 - if logging_module == picologging: + if logging_module_str == "picologging": del logging_config.handlers["console_stdout"]["stream"] get_logger = logging_config.configure() @@ -356,9 +472,9 @@ def test_customizing_handler( if configure_root_logger is True: assert isinstance(root_logger, logging_module.Logger) - assert root_logger.level == logging_module.INFO # pyright: ignore[reportGeneralTypeIssues] + assert root_logger.level == logging_module.INFO # pyright: ignore[reportGeneralTypeIssues,reportAttributeAccessIssue] - root_logger_handler = root_logger.handlers[0] # pyright: ignore[reportGeneralTypeIssues] + root_logger_handler = root_logger.handlers[0] # pyright: ignore[reportGeneralTypeIssues,reportAttributeAccessIssue] assert root_logger_handler.name == "queue_listener" assert type(root_logger_handler) is expected_root_logger_handler_class @@ -366,7 +482,8 @@ def test_customizing_handler( formatter = root_logger_handler.listener.handlers[0].formatter # type: ignore[attr-defined] else: formatter = root_logger_handler.formatter - assert formatter._fmt == log_format + if formatter is not None: + assert formatter._fmt == log_format else: # Root logger shouldn't be configured but pytest adds some handlers (for the standard `logging` module) for handler in root_logger.handlers: # type: ignore[attr-defined] @@ -381,7 +498,7 @@ def assert_logger(logger: Any) -> None: assert logger.handlers[0].formatter._fmt == log_format logger.info("Hello from '%s'", logging_module.__name__) - if logging_module == picologging: + if logging_module_str == "picologging": log_output = capsys.readouterr().err.strip() else: log_output = capsys.readouterr().out.strip() From fffcdfbdff91b97c9f79579e2ac5accbac65ca50 Mon Sep 17 00:00:00 2001 From: Cody Fincher Date: Mon, 30 Dec 2024 04:02:40 +0000 Subject: [PATCH 07/32] fix: add ignores --- pyproject.toml | 57 +++++++++++++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2a18071ef6..c94313fb11 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,21 +31,21 @@ classifiers = [ "Topic :: Software Development :: Libraries :: Python Modules", ] dependencies = [ - "anyio>=3", - "httpx>=0.22", - "exceptiongroup; python_version < \"3.11\"", - "importlib-metadata; python_version < \"3.10\"", - "importlib-resources>=5.12.0; python_version < \"3.9\"", - "msgspec>=0.18.2", - "multidict>=6.0.2", - "polyfactory>=2.6.3", - "pyyaml", - "typing-extensions", - "click", - "rich>=13.0.0", - "rich-click", - "multipart>=1.2.0", - # default litestar plugins + "anyio>=3", + "httpx>=0.22", + "exceptiongroup; python_version < \"3.11\"", + "importlib-metadata; python_version < \"3.10\"", + "importlib-resources>=5.12.0; python_version < \"3.9\"", + "msgspec>=0.18.2", + "multidict>=6.0.2", + "polyfactory>=2.6.3", + "pyyaml", + "typing-extensions", + "click", + "rich>=13.0.0", + "rich-click", + "multipart>=1.2.0", + # default litestar plugins "litestar-htmx>=0.4.0" ] description = "Litestar - A production-ready, highly performant, extensible ASGI API Framework" @@ -140,16 +140,16 @@ dev = [ ] docs = [ - "sphinx>=7.1.2", - "sphinx-autobuild>=2021.3.14", - "sphinx-copybutton>=0.5.2", - "sphinx-toolbox>=3.5.0", - "sphinx-design>=0.5.0", - "sphinx-click>=4.4.0", - "sphinxcontrib-mermaid>=0.9.2", - "auto-pytabs[sphinx]>=0.5.0", - "litestar-sphinx-theme @ git+https://github.com/litestar-org/litestar-sphinx-theme.git", - "sphinx-paramlinks>=0.6.0", + "sphinx>=7.1.2", + "sphinx-autobuild>=2021.3.14", + "sphinx-copybutton>=0.5.2", + "sphinx-toolbox>=3.5.0", + "sphinx-design>=0.5.0", + "sphinx-click>=4.4.0", + "sphinxcontrib-mermaid>=0.9.2", + "auto-pytabs[sphinx]>=0.5.0", + "litestar-sphinx-theme @ git+https://github.com/litestar-org/litestar-sphinx-theme.git", + "sphinx-paramlinks>=0.6.0", ] linting = [ "ruff>=0.2.1", @@ -256,6 +256,10 @@ module = ["tests.examples.*", "tests.docker_service_fixtures"] module = ["tests.*"] disable_error_code = ["truthy-bool"] +[[tool.mypy.overrides]] +disable_error_code = ["assignment"] +module = ["tests.unit.test_logging.*"] + [[tool.mypy.overrides]] disallow_untyped_decorators = false module = ["tests.unit.test_kwargs.test_reserved_kwargs_injection"] @@ -290,10 +294,11 @@ module = [ "litestar.plugins.pydantic.*", "tests.unit.test_contrib.test_sqlalchemy", "tests.unit.test_contrib.test_pydantic.*", + "tests.unit.test_logging.test_logging_config", "litestar.openapi.spec.base", "litestar.utils.helpers", "litestar.channels.plugin", - "litestar.handlers.http_handlers._utils" + "litestar.handlers.http_handlers._utils" ] [[tool.mypy.overrides]] From 180350dd1e9b32c55efee51c5895f6654a489d07 Mon Sep 17 00:00:00 2001 From: Cody Fincher Date: Mon, 30 Dec 2024 04:04:46 +0000 Subject: [PATCH 08/32] fix: trim whitespace --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c94313fb11..b70fd43b55 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -298,7 +298,7 @@ module = [ "litestar.openapi.spec.base", "litestar.utils.helpers", "litestar.channels.plugin", - "litestar.handlers.http_handlers._utils" + "litestar.handlers.http_handlers._utils" ] [[tool.mypy.overrides]] From 6f73ff62e2434c9d142051a1e58c653b9ffa0a16 Mon Sep 17 00:00:00 2001 From: Cody Fincher Date: Mon, 30 Dec 2024 04:02:40 +0000 Subject: [PATCH 09/32] fix: add ignores --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b70fd43b55..c94313fb11 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -298,7 +298,7 @@ module = [ "litestar.openapi.spec.base", "litestar.utils.helpers", "litestar.channels.plugin", - "litestar.handlers.http_handlers._utils" + "litestar.handlers.http_handlers._utils" ] [[tool.mypy.overrides]] From 4c6958efefed55c608676323737b82a1b2d36f7e Mon Sep 17 00:00:00 2001 From: Cody Fincher Date: Mon, 30 Dec 2024 04:04:46 +0000 Subject: [PATCH 10/32] fix: trim whitespace --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c94313fb11..b70fd43b55 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -298,7 +298,7 @@ module = [ "litestar.openapi.spec.base", "litestar.utils.helpers", "litestar.channels.plugin", - "litestar.handlers.http_handlers._utils" + "litestar.handlers.http_handlers._utils" ] [[tool.mypy.overrides]] From 288a91d30142e5b947c6383993bd8f32a91f07a2 Mon Sep 17 00:00:00 2001 From: Cody Fincher Date: Mon, 30 Dec 2024 04:02:40 +0000 Subject: [PATCH 11/32] fix: add ignores --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b70fd43b55..c94313fb11 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -298,7 +298,7 @@ module = [ "litestar.openapi.spec.base", "litestar.utils.helpers", "litestar.channels.plugin", - "litestar.handlers.http_handlers._utils" + "litestar.handlers.http_handlers._utils" ] [[tool.mypy.overrides]] From ef1e72e6a58afd523e8bab778287ea7e1183b093 Mon Sep 17 00:00:00 2001 From: Cody Fincher Date: Mon, 30 Dec 2024 04:04:46 +0000 Subject: [PATCH 12/32] fix: trim whitespace --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c94313fb11..b70fd43b55 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -298,7 +298,7 @@ module = [ "litestar.openapi.spec.base", "litestar.utils.helpers", "litestar.channels.plugin", - "litestar.handlers.http_handlers._utils" + "litestar.handlers.http_handlers._utils" ] [[tool.mypy.overrides]] From ceb7a05c7ee5bb8ae6520a4ba6bbc14395cffbe4 Mon Sep 17 00:00:00 2001 From: Cody Fincher Date: Mon, 30 Dec 2024 04:02:40 +0000 Subject: [PATCH 13/32] fix: add ignores --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b70fd43b55..c94313fb11 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -298,7 +298,7 @@ module = [ "litestar.openapi.spec.base", "litestar.utils.helpers", "litestar.channels.plugin", - "litestar.handlers.http_handlers._utils" + "litestar.handlers.http_handlers._utils" ] [[tool.mypy.overrides]] From 24159d4aaa1fd432b69ae58347f7470ac63b5baf Mon Sep 17 00:00:00 2001 From: Cody Fincher Date: Mon, 30 Dec 2024 04:04:46 +0000 Subject: [PATCH 14/32] fix: trim whitespace --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c94313fb11..b70fd43b55 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -298,7 +298,7 @@ module = [ "litestar.openapi.spec.base", "litestar.utils.helpers", "litestar.channels.plugin", - "litestar.handlers.http_handlers._utils" + "litestar.handlers.http_handlers._utils" ] [[tool.mypy.overrides]] From fc002a5e7e9d174cf6a83f2b484eb01aefd9d97a Mon Sep 17 00:00:00 2001 From: Cody Fincher Date: Mon, 30 Dec 2024 01:42:37 +0000 Subject: [PATCH 15/32] fix(channels): use `SQL` function for psycopg calls --- litestar/channels/backends/psycopg.py | 29 +++++++++++++-------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/litestar/channels/backends/psycopg.py b/litestar/channels/backends/psycopg.py index 14b53bcd1a..c7651d030c 100644 --- a/litestar/channels/backends/psycopg.py +++ b/litestar/channels/backends/psycopg.py @@ -1,19 +1,16 @@ from __future__ import annotations from contextlib import AsyncExitStack -from typing import AsyncGenerator, Iterable +from typing import Any, AsyncGenerator, Iterable -import psycopg +from psycopg import AsyncConnection +from psycopg.sql import SQL, Identifier from .base import ChannelsBackend -def _safe_quote(ident: str) -> str: - return '"{}"'.format(ident.replace('"', '""')) # sourcery skip - - class PsycoPgChannelsBackend(ChannelsBackend): - _listener_conn: psycopg.AsyncConnection + _listener_conn: AsyncConnection[Any] def __init__(self, pg_dsn: str) -> None: self._pg_dsn = pg_dsn @@ -21,7 +18,7 @@ def __init__(self, pg_dsn: str) -> None: self._exit_stack = AsyncExitStack() async def on_startup(self) -> None: - self._listener_conn = await psycopg.AsyncConnection.connect(self._pg_dsn, autocommit=True) + self._listener_conn = await AsyncConnection[Any].connect(self._pg_dsn, autocommit=True) await self._exit_stack.enter_async_context(self._listener_conn) async def on_shutdown(self) -> None: @@ -29,21 +26,23 @@ async def on_shutdown(self) -> None: async def publish(self, data: bytes, channels: Iterable[str]) -> None: dec_data = data.decode("utf-8") - async with await psycopg.AsyncConnection.connect(self._pg_dsn) as conn: + async with await AsyncConnection[Any].connect(self._pg_dsn) as conn: for channel in channels: - await conn.execute("SELECT pg_notify(%s, %s);", (channel, dec_data)) + await conn.execute(SQL("SELECT pg_notify(%s, %s);").format(Identifier(channel), dec_data)) async def subscribe(self, channels: Iterable[str]) -> None: - for channel in set(channels) - self._subscribed_channels: - # can't use placeholders in LISTEN - await self._listener_conn.execute(f"LISTEN {_safe_quote(channel)};") # pyright: ignore + channels_to_subscribe = set(channels) - self._subscribed_channels + if not channels_to_subscribe: + return + + for channel in channels_to_subscribe: + await self._listener_conn.execute(SQL("LISTEN {}").format(Identifier(channel))) self._subscribed_channels.add(channel) async def unsubscribe(self, channels: Iterable[str]) -> None: for channel in channels: - # can't use placeholders in UNLISTEN - await self._listener_conn.execute(f"UNLISTEN {_safe_quote(channel)};") # pyright: ignore + await self._listener_conn.execute(SQL("UNLISTEN {}").format(Identifier(channel))) self._subscribed_channels = self._subscribed_channels - set(channels) async def stream_events(self) -> AsyncGenerator[tuple[str, bytes], None]: From 62ce785d87b4bd63aa8be7341ed166a185b485b3 Mon Sep 17 00:00:00 2001 From: Cody Fincher Date: Mon, 30 Dec 2024 04:16:04 +0000 Subject: [PATCH 16/32] fix: revert change here --- litestar/channels/backends/psycopg.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/litestar/channels/backends/psycopg.py b/litestar/channels/backends/psycopg.py index c7651d030c..4f96c29a33 100644 --- a/litestar/channels/backends/psycopg.py +++ b/litestar/channels/backends/psycopg.py @@ -31,11 +31,7 @@ async def publish(self, data: bytes, channels: Iterable[str]) -> None: await conn.execute(SQL("SELECT pg_notify(%s, %s);").format(Identifier(channel), dec_data)) async def subscribe(self, channels: Iterable[str]) -> None: - channels_to_subscribe = set(channels) - self._subscribed_channels - if not channels_to_subscribe: - return - - for channel in channels_to_subscribe: + for channel in set(channels) - self._subscribed_channels: await self._listener_conn.execute(SQL("LISTEN {}").format(Identifier(channel))) self._subscribed_channels.add(channel) From 8c38db3df6ad0e98a1b52fcc3919d606c80f7a0a Mon Sep 17 00:00:00 2001 From: Cody Fincher Date: Mon, 30 Dec 2024 04:30:59 +0000 Subject: [PATCH 17/32] feat: re-enable safe quote --- litestar/channels/backends/psycopg.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/litestar/channels/backends/psycopg.py b/litestar/channels/backends/psycopg.py index 4f96c29a33..6ee7fe74b7 100644 --- a/litestar/channels/backends/psycopg.py +++ b/litestar/channels/backends/psycopg.py @@ -9,6 +9,10 @@ from .base import ChannelsBackend +def _safe_quote(ident: str) -> str: + return '"{}"'.format(ident.replace('"', '""')) # sourcery skip + + class PsycoPgChannelsBackend(ChannelsBackend): _listener_conn: AsyncConnection[Any] @@ -32,13 +36,13 @@ async def publish(self, data: bytes, channels: Iterable[str]) -> None: async def subscribe(self, channels: Iterable[str]) -> None: for channel in set(channels) - self._subscribed_channels: - await self._listener_conn.execute(SQL("LISTEN {}").format(Identifier(channel))) + await self._listener_conn.execute(SQL("LISTEN {}").format(Identifier(_safe_quote(channel)))) self._subscribed_channels.add(channel) async def unsubscribe(self, channels: Iterable[str]) -> None: for channel in channels: - await self._listener_conn.execute(SQL("UNLISTEN {}").format(Identifier(channel))) + await self._listener_conn.execute(SQL("UNLISTEN {}").format(Identifier(_safe_quote(channel)))) self._subscribed_channels = self._subscribed_channels - set(channels) async def stream_events(self) -> AsyncGenerator[tuple[str, bytes], None]: From d45896fbe804cee32c0bf7b9b01dff5c4758381b Mon Sep 17 00:00:00 2001 From: Cody Fincher Date: Sat, 4 Jan 2025 20:25:48 +0000 Subject: [PATCH 18/32] chore: locked deps --- uv.lock | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index 65a65a0a06..d65708e99f 100644 --- a/uv.lock +++ b/uv.lock @@ -1,11 +1,11 @@ version = 1 requires-python = ">=3.8, <4.0" resolution-markers = [ - "python_full_version < '3.9' and sys_platform != 'win32'", "python_full_version >= '3.9' and python_full_version < '3.13' and sys_platform != 'win32'", + "python_full_version < '3.9' and sys_platform != 'win32'", "python_full_version >= '3.13' and sys_platform != 'win32'", - "python_full_version < '3.9' and sys_platform == 'win32'", "python_full_version >= '3.9' and python_full_version < '3.13' and sys_platform == 'win32'", + "python_full_version < '3.9' and sys_platform == 'win32'", "python_full_version >= '3.13' and sys_platform == 'win32'", ] @@ -695,7 +695,7 @@ name = "click" version = "8.1.7" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } wheels = [ @@ -2945,6 +2945,7 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ce/ac/5b1ea50fc08a9df82de7e1771537557f07c2632231bbab652c7e22597908/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909", size = 2822712 }, { url = "https://files.pythonhosted.org/packages/c4/fc/504d4503b2abc4570fac3ca56eb8fed5e437bf9c9ef13f36b6621db8ef00/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1", size = 2920155 }, { url = "https://files.pythonhosted.org/packages/b2/d1/323581e9273ad2c0dbd1902f3fb50c441da86e894b6e25a73c3fda32c57e/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567", size = 2959356 }, + { url = "https://files.pythonhosted.org/packages/08/50/d13ea0a054189ae1bc21af1d85b6f8bb9bbc5572991055d70ad9006fe2d6/psycopg2_binary-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142", size = 2569224 }, { url = "https://files.pythonhosted.org/packages/03/a7/7aa45bea9c790da0ec4765902d714ee7c43b73ccff34916261090849b715/psycopg2_binary-2.9.10-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:eb09aa7f9cecb45027683bb55aebaaf45a0df8bf6de68801a6afdc7947bb09d4", size = 3043405 }, { url = "https://files.pythonhosted.org/packages/0e/ea/e0197035d74cc1065e94f2ebf7cdd9fa4aa00bb06b1850091568345441cd/psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b73d6d7f0ccdad7bc43e6d34273f70d587ef62f824d7261c4ae9b8b1b6af90e8", size = 2851210 }, { url = "https://files.pythonhosted.org/packages/23/bf/9be0b2dd105299860e6b001ad7519e36208944609c8382d5aa2dfc58294c/psycopg2_binary-2.9.10-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce5ab4bf46a211a8e924d307c1b1fcda82368586a19d0a24f8ae166f5c784864", size = 3080972 }, From 1ad3682b0e7d749658084ec1f72a8e3007aa03aa Mon Sep 17 00:00:00 2001 From: Cody Fincher Date: Sat, 4 Jan 2025 20:38:37 +0000 Subject: [PATCH 19/32] fix: another formatting change revert --- pyproject.toml | 166 +++++++++++++++++++++++++------------------------ 1 file changed, 84 insertions(+), 82 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index df3cfa372c..7c904dece4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,10 @@ [project] authors = [ - { name = "Cody Fincher", email = "cody@litestar.dev" }, - { name = "Jacob Coffee", email = "jacob@litestar.dev" }, - { name = "Janek Nouvertné", email = "janek@litestar.dev" }, - { name = "Na'aman Hirschfeld", email = "nhirschfeld@gmail.com" }, - { name = "Peter Schutt", email = "peter@litestar.dev" }, + {name = "Cody Fincher", email = "cody@litestar.dev"}, + {name = "Jacob Coffee", email = "jacob@litestar.dev"}, + {name = "Janek Nouvertné", email = "janek@litestar.dev"}, + {name = "Na'aman Hirschfeld", email = "nhirschfeld@gmail.com"}, + {name = "Peter Schutt", email = "peter@litestar.dev"}, ] classifiers = [ "Development Status :: 5 - Production/Stable", @@ -49,19 +49,19 @@ dependencies = [ "rich-click", "multipart>=1.2.0", # default litestar plugins - "litestar-htmx>=0.4.0" + "litestar-htmx>=0.4.0" ] description = "Litestar - A production-ready, highly performant, extensible ASGI API Framework" keywords = ["api", "rest", "asgi", "litestar", "starlite"] -license = { text = "MIT" } +license = {text = "MIT"} maintainers = [ - { name = "Litestar Developers", email = "hello@litestar.dev" }, - { name = "Cody Fincher", email = "cody@litestar.dev" }, - { name = "Jacob Coffee", email = "jacob@litestar.dev" }, - { name = "Janek Nouvertné", email = "janek@litestar.dev" }, - { name = "Peter Schutt", email = "peter@litestar.dev" }, - { name = "Visakh Unnikrishnan", email = "guacs@litestar.dev" }, - { name = "Alc", email = "alc@litestar.dev" }, + {name = "Litestar Developers", email = "hello@litestar.dev"}, + {name = "Cody Fincher", email = "cody@litestar.dev"}, + {name = "Jacob Coffee", email = "jacob@litestar.dev"}, + {name = "Janek Nouvertné", email = "janek@litestar.dev"}, + {name = "Peter Schutt", email = "peter@litestar.dev"}, + {name = "Visakh Unnikrishnan", email = "guacs@litestar.dev"}, + {name = "Alc", email = "alc@litestar.dev"} ] name = "litestar" readme = "README.md" @@ -90,7 +90,10 @@ full = [ "litestar[annotated-types,attrs,brotli,cli,cryptography,jinja,jwt,mako,minijinja,opentelemetry,piccolo,prometheus,pydantic,redis,sqlalchemy,standard,structlog,valkey]; python_version >= \"3.13\"", ] jinja = ["jinja2>=3.1.2"] -jwt = ["cryptography", "pyjwt>=2.9.0"] +jwt = [ + "cryptography", + "pyjwt>=2.9.0", +] mako = ["mako>=1.2.4"] minijinja = ["minijinja>=1.0.0"] opentelemetry = ["opentelemetry-instrumentation-asgi"] @@ -104,23 +107,20 @@ pydantic = [ "pydantic-extra-types; python_version >= \"3.9\"", ] redis = ["redis[hiredis]>=4.4.4"] +valkey = ["valkey[libvalkey]>=6.0.2"] sqlalchemy = ["advanced-alchemy>=0.2.2"] -standard = [ - "jinja2", - "jsbeautifier", - "uvicorn[standard]", - "uvloop>=0.18.0; sys_platform != 'win32'", - "fast-query-parsers>=1.0.2", -] +standard = ["jinja2", "jsbeautifier", "uvicorn[standard]", "uvloop>=0.18.0; sys_platform != 'win32'", "fast-query-parsers>=1.0.2"] structlog = ["structlog"] -valkey = ["valkey[libvalkey]>=6.0.2"] [project.scripts] litestar = "litestar.__main__:run_cli" [tool.hatch.build.targets.sdist] -include = ['docs/PYPI_README.md', '/litestar'] +include = [ + 'docs/PYPI_README.md', + '/litestar', +] [tool.uv] @@ -146,7 +146,7 @@ dev = [ "hypercorn>=0.16.0", "daphne>=4.0.0", "opentelemetry-sdk", - "httpx-sse", + "httpx-sse" ] docs = [ @@ -203,7 +203,11 @@ plugins = ["covdefaults"] source = ["litestar"] [tool.coverage.report] -exclude_lines = ['except ImportError\b', 'if VERSION.startswith("1"):', 'if pydantic.VERSION.startswith("1"):'] +exclude_lines = [ + 'except ImportError\b', + 'if VERSION.startswith("1"):', + 'if pydantic.VERSION.startswith("1"):', +] fail_under = 50 [tool.pytest.ini_options] @@ -223,7 +227,7 @@ filterwarnings = [ "ignore::DeprecationWarning:litestar.*", "ignore::pydantic.PydanticDeprecatedSince20::", "ignore:`general_plain_validator_function`:DeprecationWarning::", - "ignore: 'RichMultiCommand':DeprecationWarning::", # this is coming from rich_click itself, nothing we can do about # that for now + "ignore: 'RichMultiCommand':DeprecationWarning::", # this is coming from rich_click itself, nothing we can do about # that for now "ignore: Dropping max_length:litestar.exceptions.LitestarWarning:litestar.contrib.piccolo", "ignore: Python Debugger on exception enabled:litestar.exceptions.LitestarWarning:", "ignore: datetime.datetime.utcnow:DeprecationWarning:time_machine", @@ -236,6 +240,8 @@ testpaths = ["tests", "docs/examples/testing"] xfail_strict = true [tool.mypy] +packages = ["litestar", "tests"] +plugins = ["pydantic.mypy"] enable_error_code = [ "truthy-bool", "truthy-iterable", @@ -244,27 +250,21 @@ enable_error_code = [ "possibly-undefined", "redundant-self", ] -packages = ["litestar", "tests"] -plugins = ["pydantic.mypy"] python_version = "3.8" disallow_any_generics = false -local_partial_types = true show_error_codes = true strict = true warn_unreachable = true +local_partial_types = true [[tool.mypy.overrides]] ignore_errors = true module = ["tests.examples.*", "tests.docker_service_fixtures"] [[tool.mypy.overrides]] -disable_error_code = ["truthy-bool"] module = ["tests.*"] - -[[tool.mypy.overrides]] -disable_error_code = ["assignment"] -module = ["tests.unit.test_logging.*"] +disable_error_code = ["truthy-bool"] [[tool.mypy.overrides]] disable_error_code = ["assignment"] @@ -279,12 +279,12 @@ module = ["tests.unit.test_contrib.test_repository"] strict_equality = false [[tool.mypy.overrides]] +module = ["tests.unit.test_plugins.test_pydantic.test_openapi","litestar._asgi.routing_trie.traversal"] disable_error_code = "index, union-attr" -module = ["tests.unit.test_plugins.test_pydantic.test_openapi", "litestar._asgi.routing_trie.traversal"] [[tool.mypy.overrides]] -disable_error_code = "arg-type, comparison-overlap, unreachable" module = ["tests.unit.test_channels.test_subscriber", "tests.unit.test_response.test_streaming_response"] +disable_error_code = "arg-type, comparison-overlap, unreachable" [[tool.mypy.overrides]] ignore_missing_imports = true @@ -300,6 +300,7 @@ module = [ ] [[tool.mypy.overrides]] +warn_unused_ignores = false module = [ "litestar.contrib.sqlalchemy.*", "litestar.plugins.pydantic.*", @@ -309,19 +310,17 @@ module = [ "litestar.openapi.spec.base", "litestar.utils.helpers", "litestar.channels.plugin", - "litestar.handlers.http_handlers._utils", + "litestar.handlers.http_handlers._utils" ] -warn_unused_ignores = false [[tool.mypy.overrides]] -disable_error_code = "arg-type" -module = ["litestar.openapi.spec.base", "litestar._asgi.routin_trie.traversal", "litestar.plugins.pydantic.plugins.int"] -warn_unused_ignores = false - -[[tool.mypy.overrides]] -disable_error_code = "assignment" -module = ["tests.unit.test_logging.test_logging_config"] warn_unused_ignores = false +module = [ + "litestar.openapi.spec.base", + "litestar._asgi.routin_trie.traversal", + "litestar.plugins.pydantic.plugins.int", +] +disable_error_code = "arg-type" [tool.pydantic-mypy] init_forbid_extra = true @@ -348,6 +347,7 @@ pythonVersion = "3.8" reportUnnecessaryTypeIgnoreComments = true [tool.slotscheck] +strict-imports = false exclude-classes = """ ( # github.com/python/cpython/pull/106771 @@ -367,68 +367,70 @@ exclude-classes = """ |(^litestar.utils.sync:AsyncIteratorWrapper) ) """ -strict-imports = false [tool.ruff] -include = ["{litestar,tests,docs,test_apps,tools}/**/*.{py,pyi}", "pyproject.toml"] +include = [ + "{litestar,tests,docs,test_apps,tools}/**/*.{py,pyi}", + "pyproject.toml" +] lint.select = [ - "A", # flake8-builtins - "B", # flake8-bugbear + "A", # flake8-builtins + "B", # flake8-bugbear "BLE", # flake8-blind-except - "C4", # flake8-comprehensions + "C4", # flake8-comprehensions "C90", # mccabe - "D", # pydocstyle - "DJ", # flake8-django + "D", # pydocstyle + "DJ", # flake8-django "DTZ", # flake8-datetimez - "E", # pycodestyle errors + "E", # pycodestyle errors "ERA", # eradicate "EXE", # flake8-executable - "F", # pyflakes - "G", # flake8-logging-format - "I", # isort + "F", # pyflakes + "G", # flake8-logging-format + "I", # isort "ICN", # flake8-import-conventions "ISC", # flake8-implicit-str-concat - "N", # pep8-naming + "N", # pep8-naming "PIE", # flake8-pie "PLC", # pylint - convention "PLE", # pylint - error "PLW", # pylint - warning "PTH", # flake8-use-pathlib - "Q", # flake8-quotes + "Q", # flake8-quotes "RET", # flake8-return "RUF", # Ruff-specific rules - "S", # flake8-bandit + "S", # flake8-bandit "SIM", # flake8-simplify "T10", # flake8-debugger "T20", # flake8-print - "TC", # flake8-type-checking + "TC", # flake8-type-checking "TID", # flake8-tidy-imports - "UP", # pyupgrade - "W", # pycodestyle - warning + "UP", # pyupgrade + "W", # pycodestyle - warning "YTT", # flake8-2020 ] line-length = 120 lint.ignore = [ - "A003", # flake8-builtins - class attribute {name} is shadowing a python builtin - "B010", # flake8-bugbear - do not call setattr with a constant attribute value - "D100", # pydocstyle - missing docstring in public module - "D101", # pydocstyle - missing docstring in public class - "D102", # pydocstyle - missing docstring in public method - "D103", # pydocstyle - missing docstring in public function - "D104", # pydocstyle - missing docstring in public package - "D105", # pydocstyle - missing docstring in magic method - "D106", # pydocstyle - missing docstring in public nested class - "D107", # pydocstyle - missing docstring in __init__ - "D202", # pydocstyle - no blank lines allowed after function docstring - "D205", # pydocstyle - 1 blank line required between summary line and description - "D415", # pydocstyle - first line should end with a period, question mark, or exclamation point - "E501", # pycodestyle line too long, handled by ruff format + "A003", # flake8-builtins - class attribute {name} is shadowing a python builtin + "B010", # flake8-bugbear - do not call setattr with a constant attribute value + "D100", # pydocstyle - missing docstring in public module + "D101", # pydocstyle - missing docstring in public class + "D102", # pydocstyle - missing docstring in public method + "D103", # pydocstyle - missing docstring in public function + "D104", # pydocstyle - missing docstring in public package + "D105", # pydocstyle - missing docstring in magic method + "D106", # pydocstyle - missing docstring in public nested class + "D107", # pydocstyle - missing docstring in __init__ + "D202", # pydocstyle - no blank lines allowed after function docstring + "D205", # pydocstyle - 1 blank line required between summary line and description + "D415", # pydocstyle - first line should end with a period, question mark, or exclamation point + "E501", # pycodestyle line too long, handled by ruff format "PLW2901", # pylint - for loop variable overwritten by assignment target - "RUF012", # Ruff-specific rule - annotated with classvar - "ISC001", # Ruff formatter incompatible - "CPY001", # ruff - copyright notice at the top of the file + "RUF012", # Ruff-specific rule - annotated with classvar + "ISC001", # Ruff formatter incompatible + "CPY001", # ruff - copyright notice at the top of the file ] src = ["litestar", "tests", "docs/examples"] target-version = "py38" @@ -457,8 +459,8 @@ known-first-party = ["litestar", "tests", "examples"] "docs/examples/**" = ["T201"] "docs/examples/application_hooks/before_send_hook.py" = ["UP006"] "docs/examples/contrib/sqlalchemy/plugins/**/*.*" = ["UP006"] -"docs/examples/contrib/sqlalchemy/sqlalchemy_declarative_models.py" = ["UP006"] "docs/examples/data_transfer_objects**/*.*" = ["UP006"] +"docs/examples/contrib/sqlalchemy/sqlalchemy_declarative_models.py" = ["UP006"] "litestar/_openapi/schema_generation/schema.py" = ["C901"] "litestar/exceptions/*.*" = ["N818"] "litestar/handlers/**/*.*" = ["N801"] @@ -491,9 +493,9 @@ known-first-party = ["litestar", "tests", "examples"] "E721", ] "tests/unit/test_contrib/test_sqlalchemy/**/*.*" = ["UP006"] -"tests/unit/test_openapi/test_typescript_converter/test_converter.py" = ["W293"] "tools/**/*.*" = ["D", "ARG", "EM", "TRY", "G", "FBT"] "tools/prepare_release.py" = ["S603", "S607"] +"tests/unit/test_openapi/test_typescript_converter/test_converter.py" = ["W293"] [tool.ruff.format] docstring-code-format = true From 4c179c56e177c184eb2a032b56efffd0179a0120 Mon Sep 17 00:00:00 2001 From: Cody Fincher Date: Sun, 5 Jan 2025 05:11:56 +0000 Subject: [PATCH 20/32] fix: test change for 3.13 support --- tests/unit/test_kwargs/test_validations.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/unit/test_kwargs/test_validations.py b/tests/unit/test_kwargs/test_validations.py index f0d524b0ec..37ca305655 100644 --- a/tests/unit/test_kwargs/test_validations.py +++ b/tests/unit/test_kwargs/test_validations.py @@ -46,19 +46,19 @@ def handler(my_key: str = Parameter(**{param_field: "my_key"})) -> None: # type @pytest.mark.parametrize("reserved_kwarg", sorted(RESERVED_KWARGS)) def test_raises_when_reserved_kwargs_are_misused(reserved_kwarg: str) -> None: decorator = post if reserved_kwarg != "socket" else websocket - - exec(f"async def test_fn({reserved_kwarg}: int) -> None: pass") - handler_with_path_param = decorator("/{" + reserved_kwarg + ":int}")(locals()["test_fn"]) + local = dict(locals(), **globals()) + exec(f"async def test_fn({reserved_kwarg}: int) -> None: pass", local, local) + handler_with_path_param = decorator("/{" + reserved_kwarg + ":int}")(local["test_fn"]) with pytest.raises(ImproperlyConfiguredException): Litestar(route_handlers=[handler_with_path_param]) - exec(f"async def test_fn({reserved_kwarg}: int) -> None: pass") - handler_with_dependency = decorator("/", dependencies={reserved_kwarg: Provide(my_dependency)})(locals()["test_fn"]) + exec(f"async def test_fn({reserved_kwarg}: int) -> None: pass", local, local) + handler_with_dependency = decorator("/", dependencies={reserved_kwarg: Provide(my_dependency)})(local["test_fn"]) with pytest.raises(ImproperlyConfiguredException): Litestar(route_handlers=[handler_with_dependency]) - exec(f"async def test_fn({reserved_kwarg}: int = Parameter(query='my_param')) -> None: pass") - handler_with_aliased_param = decorator("/")(locals()["test_fn"]) + exec(f"async def test_fn({reserved_kwarg}: int = Parameter(query='my_param')) -> None: pass", local, local) + handler_with_aliased_param = decorator("/")(local["test_fn"]) with pytest.raises(ImproperlyConfiguredException): Litestar(route_handlers=[handler_with_aliased_param]) From 69123e9ab1ffe6329d6db6415d15464ba72aa80b Mon Sep 17 00:00:00 2001 From: Cody Fincher Date: Sun, 5 Jan 2025 05:49:12 +0000 Subject: [PATCH 21/32] fix: logging capsys update --- .../test_middleware/test_middleware_handling.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/tests/unit/test_middleware/test_middleware_handling.py b/tests/unit/test_middleware/test_middleware_handling.py index 790fd64f1c..eacbeb5409 100644 --- a/tests/unit/test_middleware/test_middleware_handling.py +++ b/tests/unit/test_middleware/test_middleware_handling.py @@ -3,6 +3,7 @@ from typing import TYPE_CHECKING, Any, Awaitable, Callable, List, cast import pytest +from _pytest.capture import CaptureFixture from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint from litestar import Controller, Request, Response, Router, get, post @@ -13,8 +14,6 @@ if TYPE_CHECKING: from typing import Type - from _pytest.logging import LogCaptureFixture - from litestar.types import ASGIApp, Receive, Scope, Send logger = logging.getLogger(__name__) @@ -91,7 +90,7 @@ def handler() -> None: ... assert middleware_instance.arg == 1 -def test_request_body_logging_middleware(caplog: "LogCaptureFixture") -> None: +def test_request_body_logging_middleware(capsys: "CaptureFixture[str]") -> None: @dataclass class JSONRequest: name: str @@ -102,13 +101,11 @@ class JSONRequest: def post_handler(data: JSONRequest) -> JSONRequest: return data - with caplog.at_level(logging.INFO): - client = create_test_client( - route_handlers=[post_handler], middleware=[MiddlewareProtocolRequestLoggingMiddleware] - ) - response = client.post("/", json={"name": "moishe zuchmir", "age": 40, "programmer": True}) - assert response.status_code == 201 - assert "test logging" in caplog.text + client = create_test_client(route_handlers=[post_handler], middleware=[MiddlewareProtocolRequestLoggingMiddleware]) + response = client.post("/", json={"name": "moishe zuchmir", "age": 40, "programmer": True}) + assert response.status_code == 201 + log = capsys.readouterr() + assert "test logging" in log.err def test_middleware_call_order() -> None: From a754a62f0112a1552f33066bc95d0381cae6b28a Mon Sep 17 00:00:00 2001 From: Cody Fincher Date: Sun, 5 Jan 2025 14:53:40 +0000 Subject: [PATCH 22/32] fix: sleep a bit longer for flaky tests --- tests/unit/test_channels/test_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/test_channels/test_plugin.py b/tests/unit/test_channels/test_plugin.py index 9e36633211..7ddaaf96ef 100644 --- a/tests/unit/test_channels/test_plugin.py +++ b/tests/unit/test_channels/test_plugin.py @@ -170,7 +170,7 @@ def test_create_ws_route_handlers_arbitrary_channels_allowed(channels_backend: C channels_plugin.publish("something", "foo") assert ws.receive_text(timeout=2) == "something" - time.sleep(0.1) + time.sleep(0.4) with client.websocket_connect("/ws/bar") as ws: channels_plugin.publish("something else", "bar") From 9d88e97c472d19ff1112131b4cb6c1de47a76775 Mon Sep 17 00:00:00 2001 From: Cody Fincher Date: Sun, 5 Jan 2025 15:23:08 +0000 Subject: [PATCH 23/32] feat: use updated pytest-asyncio --- Makefile | 2 +- pyproject.toml | 143 ++++++++++-------- .../test_middleware_handling.py | 25 ++- uv.lock | 26 +++- 4 files changed, 122 insertions(+), 74 deletions(-) diff --git a/Makefile b/Makefile index 3e2b026f8a..49b54ff90e 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ upgrade: ## Upgrade all dependencies to the latest stable versio .PHONY: install install: - @uv sync --all-extras --dev --python 3.12 + @uv sync --all-extras --dev --python 3.13 .PHONY: clean clean: ## Cleanup temporary build artifacts diff --git a/pyproject.toml b/pyproject.toml index d02ee0a7aa..8763a89759 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,10 @@ [project] authors = [ - {name = "Cody Fincher", email = "cody@litestar.dev"}, - {name = "Jacob Coffee", email = "jacob@litestar.dev"}, - {name = "Janek Nouvertné", email = "janek@litestar.dev"}, - {name = "Na'aman Hirschfeld", email = "nhirschfeld@gmail.com"}, - {name = "Peter Schutt", email = "peter@litestar.dev"}, + { name = "Cody Fincher", email = "cody@litestar.dev" }, + { name = "Jacob Coffee", email = "jacob@litestar.dev" }, + { name = "Janek Nouvertné", email = "janek@litestar.dev" }, + { name = "Na'aman Hirschfeld", email = "nhirschfeld@gmail.com" }, + { name = "Peter Schutt", email = "peter@litestar.dev" }, ] classifiers = [ "Development Status :: 5 - Production/Stable", @@ -49,19 +49,19 @@ dependencies = [ "rich-click", "multipart>=1.2.0", # default litestar plugins - "litestar-htmx>=0.4.0" + "litestar-htmx>=0.4.0", ] description = "Litestar - A production-ready, highly performant, extensible ASGI API Framework" keywords = ["api", "rest", "asgi", "litestar", "starlite"] -license = {text = "MIT"} +license = { text = "MIT" } maintainers = [ - {name = "Litestar Developers", email = "hello@litestar.dev"}, - {name = "Cody Fincher", email = "cody@litestar.dev"}, - {name = "Jacob Coffee", email = "jacob@litestar.dev"}, - {name = "Janek Nouvertné", email = "janek@litestar.dev"}, - {name = "Peter Schutt", email = "peter@litestar.dev"}, - {name = "Visakh Unnikrishnan", email = "guacs@litestar.dev"}, - {name = "Alc", email = "alc@litestar.dev"} + { name = "Litestar Developers", email = "hello@litestar.dev" }, + { name = "Cody Fincher", email = "cody@litestar.dev" }, + { name = "Jacob Coffee", email = "jacob@litestar.dev" }, + { name = "Janek Nouvertné", email = "janek@litestar.dev" }, + { name = "Peter Schutt", email = "peter@litestar.dev" }, + { name = "Visakh Unnikrishnan", email = "guacs@litestar.dev" }, + { name = "Alc", email = "alc@litestar.dev" }, ] name = "litestar" readme = "README.md" @@ -83,17 +83,18 @@ Twitter = "https://twitter.com/LitestarAPI" annotated-types = ["annotated-types"] attrs = ["attrs"] brotli = ["brotli"] -cli = ["jsbeautifier", "uvicorn[standard]", "uvloop>=0.18.0; sys_platform != 'win32'"] +cli = [ + "jsbeautifier", + "uvicorn[standard]", + "uvloop>=0.18.0; sys_platform != 'win32'", +] cryptography = ["cryptography"] full = [ "litestar[annotated-types,attrs,brotli,cli,cryptography,jinja,jwt,mako,minijinja,opentelemetry,piccolo,picologging,prometheus,pydantic,redis,sqlalchemy,standard,structlog,valkey]; python_version < \"3.13\"", "litestar[annotated-types,attrs,brotli,cli,cryptography,jinja,jwt,mako,minijinja,opentelemetry,piccolo,prometheus,pydantic,redis,sqlalchemy,standard,structlog,valkey]; python_version >= \"3.13\"", ] jinja = ["jinja2>=3.1.2"] -jwt = [ - "cryptography", - "pyjwt>=2.9.0", -] +jwt = ["cryptography", "pyjwt>=2.9.0"] mako = ["mako>=1.2.4"] minijinja = ["minijinja>=1.0.0"] opentelemetry = ["opentelemetry-instrumentation-asgi"] @@ -109,7 +110,13 @@ pydantic = [ redis = ["redis[hiredis]>=4.4.4"] valkey = ["valkey[libvalkey]>=6.0.2"] sqlalchemy = ["advanced-alchemy>=0.2.2"] -standard = ["jinja2", "jsbeautifier", "uvicorn[standard]", "uvloop>=0.18.0; sys_platform != 'win32'", "fast-query-parsers>=1.0.2"] +standard = [ + "jinja2", + "jsbeautifier", + "uvicorn[standard]", + "uvloop>=0.18.0; sys_platform != 'win32'", + "fast-query-parsers>=1.0.2", +] structlog = ["structlog"] [project.scripts] @@ -117,10 +124,7 @@ litestar = "litestar.__main__:run_cli" [tool.hatch.build.targets.sdist] -include = [ - 'docs/PYPI_README.md', - '/litestar', -] +include = ['docs/PYPI_README.md', '/litestar'] [tool.uv] @@ -146,7 +150,7 @@ dev = [ "hypercorn>=0.16.0", "daphne>=4.0.0", "opentelemetry-sdk", - "httpx-sse" + "httpx-sse", ] docs = [ @@ -177,7 +181,8 @@ linting = [ test = [ "covdefaults", "pytest", - "pytest-asyncio", + "pytest-asyncio; python_version < \"3.13\"", + "pytest-asyncio @ git+https://github.com/provinzkraut/pytest-asyncio.git@shutdown-asyncgens; python_version >= \"3.13\"", "pytest-cov", "pytest-lazy-fixtures", "pytest-mock", @@ -197,7 +202,11 @@ skip = 'uv.lock,docs/examples/contrib/sqlalchemy/us_state_lookup.json' [tool.coverage.run] concurrency = ["multiprocessing", "thread"] -omit = ["*/tests/*", "*/litestar/plugins/sqlalchemy.py", "*/litestar/_kwargs/types.py"] +omit = [ + "*/tests/*", + "*/litestar/plugins/sqlalchemy.py", + "*/litestar/_kwargs/types.py", +] parallel = true plugins = ["covdefaults"] source = ["litestar"] @@ -227,7 +236,7 @@ filterwarnings = [ "ignore::DeprecationWarning:litestar.*", "ignore::pydantic.PydanticDeprecatedSince20::", "ignore:`general_plain_validator_function`:DeprecationWarning::", - "ignore: 'RichMultiCommand':DeprecationWarning::", # this is coming from rich_click itself, nothing we can do about # that for now + "ignore: 'RichMultiCommand':DeprecationWarning::", # this is coming from rich_click itself, nothing we can do about # that for now "ignore: Dropping max_length:litestar.exceptions.LitestarWarning:litestar.contrib.piccolo", "ignore: Python Debugger on exception enabled:litestar.exceptions.LitestarWarning:", "ignore: datetime.datetime.utcnow:DeprecationWarning:time_machine", @@ -279,11 +288,17 @@ module = ["tests.unit.test_contrib.test_repository"] strict_equality = false [[tool.mypy.overrides]] -module = ["tests.unit.test_plugins.test_pydantic.test_openapi","litestar._asgi.routing_trie.traversal"] +module = [ + "tests.unit.test_plugins.test_pydantic.test_openapi", + "litestar._asgi.routing_trie.traversal", +] disable_error_code = "index, union-attr" [[tool.mypy.overrides]] -module = ["tests.unit.test_channels.test_subscriber", "tests.unit.test_response.test_streaming_response"] +module = [ + "tests.unit.test_channels.test_subscriber", + "tests.unit.test_response.test_streaming_response", +] disable_error_code = "arg-type, comparison-overlap, unreachable" [[tool.mypy.overrides]] @@ -310,7 +325,7 @@ module = [ "litestar.openapi.spec.base", "litestar.utils.helpers", "litestar.channels.plugin", - "litestar.handlers.http_handlers._utils" + "litestar.handlers.http_handlers._utils", ] [[tool.mypy.overrides]] @@ -371,66 +386,66 @@ exclude-classes = """ [tool.ruff] include = [ "{litestar,tests,docs,test_apps,tools}/**/*.{py,pyi}", - "pyproject.toml" + "pyproject.toml", ] lint.select = [ - "A", # flake8-builtins - "B", # flake8-bugbear + "A", # flake8-builtins + "B", # flake8-bugbear "BLE", # flake8-blind-except - "C4", # flake8-comprehensions + "C4", # flake8-comprehensions "C90", # mccabe - "D", # pydocstyle - "DJ", # flake8-django + "D", # pydocstyle + "DJ", # flake8-django "DTZ", # flake8-datetimez - "E", # pycodestyle errors + "E", # pycodestyle errors "ERA", # eradicate "EXE", # flake8-executable - "F", # pyflakes - "G", # flake8-logging-format - "I", # isort + "F", # pyflakes + "G", # flake8-logging-format + "I", # isort "ICN", # flake8-import-conventions "ISC", # flake8-implicit-str-concat - "N", # pep8-naming + "N", # pep8-naming "PIE", # flake8-pie "PLC", # pylint - convention "PLE", # pylint - error "PLW", # pylint - warning "PTH", # flake8-use-pathlib - "Q", # flake8-quotes + "Q", # flake8-quotes "RET", # flake8-return "RUF", # Ruff-specific rules - "S", # flake8-bandit + "S", # flake8-bandit "SIM", # flake8-simplify "T10", # flake8-debugger "T20", # flake8-print - "TC", # flake8-type-checking + "TC", # flake8-type-checking "TID", # flake8-tidy-imports - "UP", # pyupgrade - "W", # pycodestyle - warning + "UP", # pyupgrade + "W", # pycodestyle - warning "YTT", # flake8-2020 ] line-length = 120 lint.ignore = [ - "A003", # flake8-builtins - class attribute {name} is shadowing a python builtin - "B010", # flake8-bugbear - do not call setattr with a constant attribute value - "D100", # pydocstyle - missing docstring in public module - "D101", # pydocstyle - missing docstring in public class - "D102", # pydocstyle - missing docstring in public method - "D103", # pydocstyle - missing docstring in public function - "D104", # pydocstyle - missing docstring in public package - "D105", # pydocstyle - missing docstring in magic method - "D106", # pydocstyle - missing docstring in public nested class - "D107", # pydocstyle - missing docstring in __init__ - "D202", # pydocstyle - no blank lines allowed after function docstring - "D205", # pydocstyle - 1 blank line required between summary line and description - "D415", # pydocstyle - first line should end with a period, question mark, or exclamation point - "E501", # pycodestyle line too long, handled by ruff format + "A003", # flake8-builtins - class attribute {name} is shadowing a python builtin + "B010", # flake8-bugbear - do not call setattr with a constant attribute value + "D100", # pydocstyle - missing docstring in public module + "D101", # pydocstyle - missing docstring in public class + "D102", # pydocstyle - missing docstring in public method + "D103", # pydocstyle - missing docstring in public function + "D104", # pydocstyle - missing docstring in public package + "D105", # pydocstyle - missing docstring in magic method + "D106", # pydocstyle - missing docstring in public nested class + "D107", # pydocstyle - missing docstring in __init__ + "D202", # pydocstyle - no blank lines allowed after function docstring + "D205", # pydocstyle - 1 blank line required between summary line and description + "D415", # pydocstyle - first line should end with a period, question mark, or exclamation point + "E501", # pycodestyle line too long, handled by ruff format "PLW2901", # pylint - for loop variable overwritten by assignment target - "RUF012", # Ruff-specific rule - annotated with classvar - "ISC001", # Ruff formatter incompatible - "CPY001", # ruff - copyright notice at the top of the file + "RUF012", # Ruff-specific rule - annotated with classvar + "ISC001", # Ruff formatter incompatible + "CPY001", # ruff - copyright notice at the top of the file ] src = ["litestar", "tests", "docs/examples"] target-version = "py38" diff --git a/tests/unit/test_middleware/test_middleware_handling.py b/tests/unit/test_middleware/test_middleware_handling.py index eacbeb5409..e038796502 100644 --- a/tests/unit/test_middleware/test_middleware_handling.py +++ b/tests/unit/test_middleware/test_middleware_handling.py @@ -1,9 +1,11 @@ import logging +import sys from dataclasses import dataclass from typing import TYPE_CHECKING, Any, Awaitable, Callable, List, cast import pytest from _pytest.capture import CaptureFixture +from _pytest.logging import LogCaptureFixture from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint from litestar import Controller, Request, Response, Router, get, post @@ -90,7 +92,7 @@ def handler() -> None: ... assert middleware_instance.arg == 1 -def test_request_body_logging_middleware(capsys: "CaptureFixture[str]") -> None: +def test_request_body_logging_middleware(caplog: LogCaptureFixture, capsys: "CaptureFixture[str]") -> None: @dataclass class JSONRequest: name: str @@ -101,11 +103,22 @@ class JSONRequest: def post_handler(data: JSONRequest) -> JSONRequest: return data - client = create_test_client(route_handlers=[post_handler], middleware=[MiddlewareProtocolRequestLoggingMiddleware]) - response = client.post("/", json={"name": "moishe zuchmir", "age": 40, "programmer": True}) - assert response.status_code == 201 - log = capsys.readouterr() - assert "test logging" in log.err + if sys.version_info < (3, 13): + with caplog.at_level(logging.INFO): + client = create_test_client( + route_handlers=[post_handler], middleware=[MiddlewareProtocolRequestLoggingMiddleware] + ) + response = client.post("/", json={"name": "moishe zuchmir", "age": 40, "programmer": True}) + assert response.status_code == 201 + assert "test logging" in caplog.text + else: + client = create_test_client( + route_handlers=[post_handler], middleware=[MiddlewareProtocolRequestLoggingMiddleware] + ) + response = client.post("/", json={"name": "moishe zuchmir", "age": 40, "programmer": True}) + assert response.status_code == 201 + log = capsys.readouterr() + assert "test logging" in log.err def test_middleware_call_order() -> None: diff --git a/uv.lock b/uv.lock index 23a1c1e5cf..10e6825be4 100644 --- a/uv.lock +++ b/uv.lock @@ -1911,7 +1911,8 @@ linting = [ test = [ { name = "covdefaults" }, { name = "pytest" }, - { name = "pytest-asyncio" }, + { name = "pytest-asyncio", version = "0.1.dev895+g761f742", source = { git = "https://github.com/provinzkraut/pytest-asyncio.git?rev=shutdown-asyncgens#761f742c2b7c50bc0a03b5f3528e2738b18e2ac8" }, marker = "python_full_version >= '3.13'" }, + { name = "pytest-asyncio", version = "0.23.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, { name = "pytest-cov" }, { name = "pytest-lazy-fixtures" }, { name = "pytest-mock" }, @@ -2023,7 +2024,8 @@ linting = [ test = [ { name = "covdefaults" }, { name = "pytest" }, - { name = "pytest-asyncio" }, + { name = "pytest-asyncio", marker = "python_full_version < '3.13'" }, + { name = "pytest-asyncio", marker = "python_full_version >= '3.13'", git = "https://github.com/provinzkraut/pytest-asyncio.git?rev=shutdown-asyncgens" }, { name = "pytest-cov" }, { name = "pytest-lazy-fixtures" }, { name = "pytest-mock" }, @@ -3287,12 +3289,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 }, ] +[[package]] +name = "pytest-asyncio" +version = "0.1.dev895+g761f742" +source = { git = "https://github.com/provinzkraut/pytest-asyncio.git?rev=shutdown-asyncgens#761f742c2b7c50bc0a03b5f3528e2738b18e2ac8" } +resolution-markers = [ + "python_full_version >= '3.13' and sys_platform != 'win32'", + "python_full_version >= '3.13' and sys_platform == 'win32'", +] +dependencies = [ + { name = "pytest", marker = "python_full_version >= '3.13'" }, +] + [[package]] name = "pytest-asyncio" version = "0.23.8" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.9' and python_full_version < '3.13' and sys_platform != 'win32'", + "python_full_version < '3.9' and sys_platform != 'win32'", + "python_full_version >= '3.9' and python_full_version < '3.13' and sys_platform == 'win32'", + "python_full_version < '3.9' and sys_platform == 'win32'", +] dependencies = [ - { name = "pytest" }, + { name = "pytest", marker = "python_full_version < '3.13'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/de/b4/0b378b7bf26a8ae161c3890c0b48a91a04106c5713ce81b4b080ea2f4f18/pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3", size = 46920 } wheels = [ From 5439d49e749bf5ef20ad92fc6363df383d5c9a18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janek=20Nouvertn=C3=A9?= <25355197+provinzkraut@users.noreply.github.com> Date: Sun, 5 Jan 2025 17:00:39 +0100 Subject: [PATCH 24/32] fix some resource handling --- litestar/middleware/compression/middleware.py | 5 +++++ tests/unit/test_middleware/test_compression_middleware.py | 2 ++ 2 files changed, 7 insertions(+) diff --git a/litestar/middleware/compression/middleware.py b/litestar/middleware/compression/middleware.py index 7ea7853b08..21d70a66ff 100644 --- a/litestar/middleware/compression/middleware.py +++ b/litestar/middleware/compression/middleware.py @@ -131,6 +131,11 @@ async def send_wrapper(message: Message) -> None: if initial_message is not None and value_or_default(connection_state.is_cached, False): await send(initial_message) await send(message) + facade.close() + return + + if initial_message and message["type"] == "http.disconnect": + facade.close() return if initial_message and message["type"] == "http.response.body": diff --git a/tests/unit/test_middleware/test_compression_middleware.py b/tests/unit/test_middleware/test_compression_middleware.py index 7c5f04ec07..a643e54271 100644 --- a/tests/unit/test_middleware/test_compression_middleware.py +++ b/tests/unit/test_middleware/test_compression_middleware.py @@ -196,6 +196,8 @@ async def fake_send(message: Message) -> None: # second body message with more_body=True will be empty if zlib buffers output and is not flushed await wrapped_send(HTTPResponseBodyEvent(type="http.response.body", body=b"abc", more_body=True)) assert mock.mock_calls[-1].args[0]["body"] + # send a more_body=False + await wrapped_send(HTTPResponseBodyEvent(type="http.response.body", body=b"", more_body=False)) @pytest.mark.parametrize( From 96828b01f1568c651e0e85abb9561c9dbf7620ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janek=20Nouvertn=C3=A9?= <25355197+provinzkraut@users.noreply.github.com> Date: Sun, 5 Jan 2025 17:05:22 +0100 Subject: [PATCH 25/32] properly close compression facade in all cases --- litestar/middleware/compression/middleware.py | 1 + tests/unit/test_middleware/test_compression_middleware.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/litestar/middleware/compression/middleware.py b/litestar/middleware/compression/middleware.py index 21d70a66ff..5458bcc8d7 100644 --- a/litestar/middleware/compression/middleware.py +++ b/litestar/middleware/compression/middleware.py @@ -175,6 +175,7 @@ async def send_wrapper(message: Message) -> None: await send(message) else: + facade.close() await send(initial_message) await send(message) diff --git a/tests/unit/test_middleware/test_compression_middleware.py b/tests/unit/test_middleware/test_compression_middleware.py index a643e54271..1feb001bb5 100644 --- a/tests/unit/test_middleware/test_compression_middleware.py +++ b/tests/unit/test_middleware/test_compression_middleware.py @@ -196,7 +196,7 @@ async def fake_send(message: Message) -> None: # second body message with more_body=True will be empty if zlib buffers output and is not flushed await wrapped_send(HTTPResponseBodyEvent(type="http.response.body", body=b"abc", more_body=True)) assert mock.mock_calls[-1].args[0]["body"] - # send a more_body=False + # send a more_body=False so resources close properly await wrapped_send(HTTPResponseBodyEvent(type="http.response.body", body=b"", more_body=False)) From 2f7a3e80558f638634b6f8f96692f2607a66f43c Mon Sep 17 00:00:00 2001 From: Cody Fincher Date: Sun, 5 Jan 2025 16:21:45 +0000 Subject: [PATCH 26/32] fix: remove toml linting changes --- pyproject.toml | 140 ++++++++++++++++++++++--------------------------- 1 file changed, 63 insertions(+), 77 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8763a89759..6692374e56 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,10 @@ [project] authors = [ - { name = "Cody Fincher", email = "cody@litestar.dev" }, - { name = "Jacob Coffee", email = "jacob@litestar.dev" }, - { name = "Janek Nouvertné", email = "janek@litestar.dev" }, - { name = "Na'aman Hirschfeld", email = "nhirschfeld@gmail.com" }, - { name = "Peter Schutt", email = "peter@litestar.dev" }, + {name = "Cody Fincher", email = "cody@litestar.dev"}, + {name = "Jacob Coffee", email = "jacob@litestar.dev"}, + {name = "Janek Nouvertné", email = "janek@litestar.dev"}, + {name = "Na'aman Hirschfeld", email = "nhirschfeld@gmail.com"}, + {name = "Peter Schutt", email = "peter@litestar.dev"}, ] classifiers = [ "Development Status :: 5 - Production/Stable", @@ -49,19 +49,19 @@ dependencies = [ "rich-click", "multipart>=1.2.0", # default litestar plugins - "litestar-htmx>=0.4.0", + "litestar-htmx>=0.4.0" ] description = "Litestar - A production-ready, highly performant, extensible ASGI API Framework" keywords = ["api", "rest", "asgi", "litestar", "starlite"] -license = { text = "MIT" } +license = {text = "MIT"} maintainers = [ - { name = "Litestar Developers", email = "hello@litestar.dev" }, - { name = "Cody Fincher", email = "cody@litestar.dev" }, - { name = "Jacob Coffee", email = "jacob@litestar.dev" }, - { name = "Janek Nouvertné", email = "janek@litestar.dev" }, - { name = "Peter Schutt", email = "peter@litestar.dev" }, - { name = "Visakh Unnikrishnan", email = "guacs@litestar.dev" }, - { name = "Alc", email = "alc@litestar.dev" }, + {name = "Litestar Developers", email = "hello@litestar.dev"}, + {name = "Cody Fincher", email = "cody@litestar.dev"}, + {name = "Jacob Coffee", email = "jacob@litestar.dev"}, + {name = "Janek Nouvertné", email = "janek@litestar.dev"}, + {name = "Peter Schutt", email = "peter@litestar.dev"}, + {name = "Visakh Unnikrishnan", email = "guacs@litestar.dev"}, + {name = "Alc", email = "alc@litestar.dev"} ] name = "litestar" readme = "README.md" @@ -83,18 +83,17 @@ Twitter = "https://twitter.com/LitestarAPI" annotated-types = ["annotated-types"] attrs = ["attrs"] brotli = ["brotli"] -cli = [ - "jsbeautifier", - "uvicorn[standard]", - "uvloop>=0.18.0; sys_platform != 'win32'", -] +cli = ["jsbeautifier", "uvicorn[standard]", "uvloop>=0.18.0; sys_platform != 'win32'"] cryptography = ["cryptography"] full = [ "litestar[annotated-types,attrs,brotli,cli,cryptography,jinja,jwt,mako,minijinja,opentelemetry,piccolo,picologging,prometheus,pydantic,redis,sqlalchemy,standard,structlog,valkey]; python_version < \"3.13\"", "litestar[annotated-types,attrs,brotli,cli,cryptography,jinja,jwt,mako,minijinja,opentelemetry,piccolo,prometheus,pydantic,redis,sqlalchemy,standard,structlog,valkey]; python_version >= \"3.13\"", ] jinja = ["jinja2>=3.1.2"] -jwt = ["cryptography", "pyjwt>=2.9.0"] +jwt = [ + "cryptography", + "pyjwt>=2.9.0", +] mako = ["mako>=1.2.4"] minijinja = ["minijinja>=1.0.0"] opentelemetry = ["opentelemetry-instrumentation-asgi"] @@ -110,13 +109,7 @@ pydantic = [ redis = ["redis[hiredis]>=4.4.4"] valkey = ["valkey[libvalkey]>=6.0.2"] sqlalchemy = ["advanced-alchemy>=0.2.2"] -standard = [ - "jinja2", - "jsbeautifier", - "uvicorn[standard]", - "uvloop>=0.18.0; sys_platform != 'win32'", - "fast-query-parsers>=1.0.2", -] +standard = ["jinja2", "jsbeautifier", "uvicorn[standard]", "uvloop>=0.18.0; sys_platform != 'win32'", "fast-query-parsers>=1.0.2"] structlog = ["structlog"] [project.scripts] @@ -124,7 +117,10 @@ litestar = "litestar.__main__:run_cli" [tool.hatch.build.targets.sdist] -include = ['docs/PYPI_README.md', '/litestar'] +include = [ + 'docs/PYPI_README.md', + '/litestar', +] [tool.uv] @@ -150,7 +146,7 @@ dev = [ "hypercorn>=0.16.0", "daphne>=4.0.0", "opentelemetry-sdk", - "httpx-sse", + "httpx-sse" ] docs = [ @@ -202,11 +198,7 @@ skip = 'uv.lock,docs/examples/contrib/sqlalchemy/us_state_lookup.json' [tool.coverage.run] concurrency = ["multiprocessing", "thread"] -omit = [ - "*/tests/*", - "*/litestar/plugins/sqlalchemy.py", - "*/litestar/_kwargs/types.py", -] +omit = ["*/tests/*", "*/litestar/plugins/sqlalchemy.py", "*/litestar/_kwargs/types.py"] parallel = true plugins = ["covdefaults"] source = ["litestar"] @@ -236,7 +228,7 @@ filterwarnings = [ "ignore::DeprecationWarning:litestar.*", "ignore::pydantic.PydanticDeprecatedSince20::", "ignore:`general_plain_validator_function`:DeprecationWarning::", - "ignore: 'RichMultiCommand':DeprecationWarning::", # this is coming from rich_click itself, nothing we can do about # that for now + "ignore: 'RichMultiCommand':DeprecationWarning::", # this is coming from rich_click itself, nothing we can do about # that for now "ignore: Dropping max_length:litestar.exceptions.LitestarWarning:litestar.contrib.piccolo", "ignore: Python Debugger on exception enabled:litestar.exceptions.LitestarWarning:", "ignore: datetime.datetime.utcnow:DeprecationWarning:time_machine", @@ -288,17 +280,11 @@ module = ["tests.unit.test_contrib.test_repository"] strict_equality = false [[tool.mypy.overrides]] -module = [ - "tests.unit.test_plugins.test_pydantic.test_openapi", - "litestar._asgi.routing_trie.traversal", -] +module = ["tests.unit.test_plugins.test_pydantic.test_openapi","litestar._asgi.routing_trie.traversal"] disable_error_code = "index, union-attr" [[tool.mypy.overrides]] -module = [ - "tests.unit.test_channels.test_subscriber", - "tests.unit.test_response.test_streaming_response", -] +module = ["tests.unit.test_channels.test_subscriber", "tests.unit.test_response.test_streaming_response"] disable_error_code = "arg-type, comparison-overlap, unreachable" [[tool.mypy.overrides]] @@ -325,7 +311,7 @@ module = [ "litestar.openapi.spec.base", "litestar.utils.helpers", "litestar.channels.plugin", - "litestar.handlers.http_handlers._utils", + "litestar.handlers.http_handlers._utils" ] [[tool.mypy.overrides]] @@ -386,66 +372,66 @@ exclude-classes = """ [tool.ruff] include = [ "{litestar,tests,docs,test_apps,tools}/**/*.{py,pyi}", - "pyproject.toml", + "pyproject.toml" ] lint.select = [ - "A", # flake8-builtins - "B", # flake8-bugbear + "A", # flake8-builtins + "B", # flake8-bugbear "BLE", # flake8-blind-except - "C4", # flake8-comprehensions + "C4", # flake8-comprehensions "C90", # mccabe - "D", # pydocstyle - "DJ", # flake8-django + "D", # pydocstyle + "DJ", # flake8-django "DTZ", # flake8-datetimez - "E", # pycodestyle errors + "E", # pycodestyle errors "ERA", # eradicate "EXE", # flake8-executable - "F", # pyflakes - "G", # flake8-logging-format - "I", # isort + "F", # pyflakes + "G", # flake8-logging-format + "I", # isort "ICN", # flake8-import-conventions "ISC", # flake8-implicit-str-concat - "N", # pep8-naming + "N", # pep8-naming "PIE", # flake8-pie "PLC", # pylint - convention "PLE", # pylint - error "PLW", # pylint - warning "PTH", # flake8-use-pathlib - "Q", # flake8-quotes + "Q", # flake8-quotes "RET", # flake8-return "RUF", # Ruff-specific rules - "S", # flake8-bandit + "S", # flake8-bandit "SIM", # flake8-simplify "T10", # flake8-debugger "T20", # flake8-print - "TC", # flake8-type-checking + "TC", # flake8-type-checking "TID", # flake8-tidy-imports - "UP", # pyupgrade - "W", # pycodestyle - warning + "UP", # pyupgrade + "W", # pycodestyle - warning "YTT", # flake8-2020 ] line-length = 120 lint.ignore = [ - "A003", # flake8-builtins - class attribute {name} is shadowing a python builtin - "B010", # flake8-bugbear - do not call setattr with a constant attribute value - "D100", # pydocstyle - missing docstring in public module - "D101", # pydocstyle - missing docstring in public class - "D102", # pydocstyle - missing docstring in public method - "D103", # pydocstyle - missing docstring in public function - "D104", # pydocstyle - missing docstring in public package - "D105", # pydocstyle - missing docstring in magic method - "D106", # pydocstyle - missing docstring in public nested class - "D107", # pydocstyle - missing docstring in __init__ - "D202", # pydocstyle - no blank lines allowed after function docstring - "D205", # pydocstyle - 1 blank line required between summary line and description - "D415", # pydocstyle - first line should end with a period, question mark, or exclamation point - "E501", # pycodestyle line too long, handled by ruff format + "A003", # flake8-builtins - class attribute {name} is shadowing a python builtin + "B010", # flake8-bugbear - do not call setattr with a constant attribute value + "D100", # pydocstyle - missing docstring in public module + "D101", # pydocstyle - missing docstring in public class + "D102", # pydocstyle - missing docstring in public method + "D103", # pydocstyle - missing docstring in public function + "D104", # pydocstyle - missing docstring in public package + "D105", # pydocstyle - missing docstring in magic method + "D106", # pydocstyle - missing docstring in public nested class + "D107", # pydocstyle - missing docstring in __init__ + "D202", # pydocstyle - no blank lines allowed after function docstring + "D205", # pydocstyle - 1 blank line required between summary line and description + "D415", # pydocstyle - first line should end with a period, question mark, or exclamation point + "E501", # pycodestyle line too long, handled by ruff format "PLW2901", # pylint - for loop variable overwritten by assignment target - "RUF012", # Ruff-specific rule - annotated with classvar - "ISC001", # Ruff formatter incompatible - "CPY001", # ruff - copyright notice at the top of the file + "RUF012", # Ruff-specific rule - annotated with classvar + "ISC001", # Ruff formatter incompatible + "CPY001", # ruff - copyright notice at the top of the file ] src = ["litestar", "tests", "docs/examples"] target-version = "py38" From 2a834f2cb36ed241ca8569a07a35c471a33ddb24 Mon Sep 17 00:00:00 2001 From: Cody Fincher Date: Sun, 5 Jan 2025 16:24:30 +0000 Subject: [PATCH 27/32] fix: revert default installed version --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 49b54ff90e..3e2b026f8a 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ upgrade: ## Upgrade all dependencies to the latest stable versio .PHONY: install install: - @uv sync --all-extras --dev --python 3.13 + @uv sync --all-extras --dev --python 3.12 .PHONY: clean clean: ## Cleanup temporary build artifacts From 8991fd0148b084dab1be242759660f7f11969bd1 Mon Sep 17 00:00:00 2001 From: Cody Fincher Date: Sun, 5 Jan 2025 17:14:59 +0000 Subject: [PATCH 28/32] fix: simplify anyio dep --- pyproject.toml | 4 ++-- uv.lock | 48 ++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6692374e56..4d9068e30b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,8 +32,7 @@ classifiers = [ "Topic :: Software Development :: Libraries :: Python Modules", ] dependencies = [ - "anyio>=3 ; python_version < \"3.9\"", - "anyio>=4 ; python_version >= \"3.9\"", + "anyio>=3", "httpx>=0.22", "exceptiongroup; python_version < \"3.11\"", "importlib-metadata; python_version < \"3.10\"", @@ -213,6 +212,7 @@ fail_under = 50 [tool.pytest.ini_options] addopts = "--strict-markers --strict-config --dist=loadgroup -m 'not server_integration'" +asyncio_default_fixture_loop_scope = "function" asyncio_mode = "auto" filterwarnings = [ "error", diff --git a/uv.lock b/uv.lock index 10e6825be4..d1fa215356 100644 --- a/uv.lock +++ b/uv.lock @@ -90,17 +90,42 @@ wheels = [ name = "anyio" version = "4.5.2" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9' and sys_platform != 'win32'", + "python_full_version < '3.9' and sys_platform == 'win32'", +] dependencies = [ - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "idna" }, - { name = "sniffio" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.9'" }, + { name = "idna", marker = "python_full_version < '3.9'" }, + { name = "sniffio", marker = "python_full_version < '3.9'" }, + { name = "typing-extensions", marker = "python_full_version < '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4d/f9/9a7ce600ebe7804daf90d4d48b1c0510a4561ddce43a596be46676f82343/anyio-4.5.2.tar.gz", hash = "sha256:23009af4ed04ce05991845451e11ef02fc7c5ed29179ac9a420e5ad0ac7ddc5b", size = 171293 } wheels = [ { url = "https://files.pythonhosted.org/packages/1b/b4/f7e396030e3b11394436358ca258a81d6010106582422f23443c16ca1873/anyio-4.5.2-py3-none-any.whl", hash = "sha256:c011ee36bc1e8ba40e5a81cb9df91925c218fe9b778554e0b56a21e1b5d4716f", size = 89766 }, ] +[[package]] +name = "anyio" +version = "4.8.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.9' and python_full_version < '3.13' and sys_platform != 'win32'", + "python_full_version >= '3.13' and sys_platform != 'win32'", + "python_full_version >= '3.9' and python_full_version < '3.13' and sys_platform == 'win32'", + "python_full_version >= '3.13' and sys_platform == 'win32'", +] +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, + { name = "idna", marker = "python_full_version >= '3.9'" }, + { name = "sniffio", marker = "python_full_version >= '3.9'" }, + { name = "typing-extensions", marker = "python_full_version >= '3.9' and python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/73/199a98fc2dae33535d6b8e8e6ec01f8c1d76c9adb096c6b7d64823038cde/anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a", size = 181126 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/eb/e7f063ad1fec6b3178a3cd82d1a3c4de82cccf283fc42746168188e1cdd5/anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a", size = 96041 }, +] + [[package]] name = "apeye" version = "1.4.1" @@ -1361,7 +1386,8 @@ name = "httpx" version = "0.28.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "anyio" }, + { name = "anyio", version = "4.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "anyio", version = "4.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "certifi" }, { name = "httpcore" }, { name = "idna" }, @@ -1749,7 +1775,8 @@ name = "litestar" version = "2.13.0" source = { editable = "." } dependencies = [ - { name = "anyio" }, + { name = "anyio", version = "4.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "anyio", version = "4.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "click" }, { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "httpx" }, @@ -1926,8 +1953,7 @@ test = [ requires-dist = [ { name = "advanced-alchemy", marker = "extra == 'sqlalchemy'", specifier = ">=0.2.2" }, { name = "annotated-types", marker = "extra == 'annotated-types'" }, - { name = "anyio", marker = "python_full_version < '3.9'", specifier = ">=3" }, - { name = "anyio", marker = "python_full_version >= '3.9'", specifier = ">=4" }, + { name = "anyio", specifier = ">=3" }, { name = "attrs", marker = "extra == 'attrs'" }, { name = "brotli", marker = "extra == 'brotli'" }, { name = "click" }, @@ -4038,7 +4064,8 @@ name = "starlette" version = "0.41.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "anyio" }, + { name = "anyio", version = "4.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "anyio", version = "4.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "typing-extensions", marker = "python_full_version < '3.10'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1a/4c/9b5764bd22eec91c4039ef4c55334e9187085da2d8a2df7bd570869aae18/starlette-0.41.3.tar.gz", hash = "sha256:0e4ab3d16522a255be6b28260b938eae2482f98ce5cc934cb08dce8dc3ba5835", size = 2574159 } @@ -4545,7 +4572,8 @@ name = "watchfiles" version = "0.24.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "anyio" }, + { name = "anyio", version = "4.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "anyio", version = "4.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c8/27/2ba23c8cc85796e2d41976439b08d52f691655fdb9401362099502d1f0cf/watchfiles-0.24.0.tar.gz", hash = "sha256:afb72325b74fa7a428c009c1b8be4b4d7c2afedafb2982827ef2156646df2fe1", size = 37870 } wheels = [ From 8e73bbe2da63c98457fe781c710a9ff417bd071c Mon Sep 17 00:00:00 2001 From: Cody Fincher Date: Sun, 5 Jan 2025 17:17:33 +0000 Subject: [PATCH 29/32] fix: simplify msgspec dep --- pyproject.toml | 3 +-- uv.lock | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 4d9068e30b..900c70a954 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,8 +37,7 @@ dependencies = [ "exceptiongroup; python_version < \"3.11\"", "importlib-metadata; python_version < \"3.10\"", "importlib-resources>=5.12.0; python_version < \"3.9\"", - "msgspec>=0.18.2,<0.19.0; python_version < \"3.9\"", - "msgspec>=0.19.0; python_version >= \"3.9\"", + "msgspec>=0.18.2", "multidict>=6.0.2", "polyfactory>=2.6.3", "pyyaml", diff --git a/uv.lock b/uv.lock index d1fa215356..a907451e72 100644 --- a/uv.lock +++ b/uv.lock @@ -1974,8 +1974,7 @@ requires-dist = [ { name = "litestar-htmx", specifier = ">=0.4.0" }, { name = "mako", marker = "extra == 'mako'", specifier = ">=1.2.4" }, { name = "minijinja", marker = "extra == 'minijinja'", specifier = ">=1.0.0" }, - { name = "msgspec", marker = "python_full_version < '3.9'", specifier = ">=0.18.2,<0.19.0" }, - { name = "msgspec", marker = "python_full_version >= '3.9'", specifier = ">=0.19.0" }, + { name = "msgspec", specifier = ">=0.18.2" }, { name = "multidict", specifier = ">=6.0.2" }, { name = "multipart", specifier = ">=1.2.0" }, { name = "opentelemetry-instrumentation-asgi", marker = "extra == 'opentelemetry'" }, From 7eca82e8093f44af7a341b3cac8176f177c065e6 Mon Sep 17 00:00:00 2001 From: Cody Fincher Date: Sun, 5 Jan 2025 17:20:45 +0000 Subject: [PATCH 30/32] fix: remove new feature for now --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 900c70a954..f04ea69eb8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -211,7 +211,6 @@ fail_under = 50 [tool.pytest.ini_options] addopts = "--strict-markers --strict-config --dist=loadgroup -m 'not server_integration'" -asyncio_default_fixture_loop_scope = "function" asyncio_mode = "auto" filterwarnings = [ "error", From c01af15dd9c9f45afb9c08c06b14825552cda973 Mon Sep 17 00:00:00 2001 From: Cody Fincher Date: Wed, 8 Jan 2025 15:47:44 +0000 Subject: [PATCH 31/32] fix: update `pytest-asyncio` --- pyproject.toml | 3 +-- uv.lock | 33 ++++++++++++++++++--------------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f04ea69eb8..91493618fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -175,8 +175,7 @@ linting = [ test = [ "covdefaults", "pytest", - "pytest-asyncio; python_version < \"3.13\"", - "pytest-asyncio @ git+https://github.com/provinzkraut/pytest-asyncio.git@shutdown-asyncgens; python_version >= \"3.13\"", + "pytest-asyncio", "pytest-cov", "pytest-lazy-fixtures", "pytest-mock", diff --git a/uv.lock b/uv.lock index a907451e72..b912672024 100644 --- a/uv.lock +++ b/uv.lock @@ -1938,8 +1938,8 @@ linting = [ test = [ { name = "covdefaults" }, { name = "pytest" }, - { name = "pytest-asyncio", version = "0.1.dev895+g761f742", source = { git = "https://github.com/provinzkraut/pytest-asyncio.git?rev=shutdown-asyncgens#761f742c2b7c50bc0a03b5f3528e2738b18e2ac8" }, marker = "python_full_version >= '3.13'" }, - { name = "pytest-asyncio", version = "0.23.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.13'" }, + { name = "pytest-asyncio", version = "0.24.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "pytest-asyncio", version = "0.25.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, { name = "pytest-cov" }, { name = "pytest-lazy-fixtures" }, { name = "pytest-mock" }, @@ -2049,8 +2049,7 @@ linting = [ test = [ { name = "covdefaults" }, { name = "pytest" }, - { name = "pytest-asyncio", marker = "python_full_version < '3.13'" }, - { name = "pytest-asyncio", marker = "python_full_version >= '3.13'", git = "https://github.com/provinzkraut/pytest-asyncio.git?rev=shutdown-asyncgens" }, + { name = "pytest-asyncio" }, { name = "pytest-cov" }, { name = "pytest-lazy-fixtures" }, { name = "pytest-mock" }, @@ -3316,32 +3315,36 @@ wheels = [ [[package]] name = "pytest-asyncio" -version = "0.1.dev895+g761f742" -source = { git = "https://github.com/provinzkraut/pytest-asyncio.git?rev=shutdown-asyncgens#761f742c2b7c50bc0a03b5f3528e2738b18e2ac8" } +version = "0.24.0" +source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.13' and sys_platform != 'win32'", - "python_full_version >= '3.13' and sys_platform == 'win32'", + "python_full_version < '3.9' and sys_platform != 'win32'", + "python_full_version < '3.9' and sys_platform == 'win32'", ] dependencies = [ - { name = "pytest", marker = "python_full_version >= '3.13'" }, + { name = "pytest", marker = "python_full_version < '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/52/6d/c6cf50ce320cf8611df7a1254d86233b3df7cc07f9b5f5cbcb82e08aa534/pytest_asyncio-0.24.0.tar.gz", hash = "sha256:d081d828e576d85f875399194281e92bf8a68d60d72d1a2faf2feddb6c46b276", size = 49855 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/31/6607dab48616902f76885dfcf62c08d929796fc3b2d2318faf9fd54dbed9/pytest_asyncio-0.24.0-py3-none-any.whl", hash = "sha256:a811296ed596b69bf0b6f3dc40f83bcaf341b155a269052d82efa2b25ac7037b", size = 18024 }, ] [[package]] name = "pytest-asyncio" -version = "0.23.8" +version = "0.25.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.9' and python_full_version < '3.13' and sys_platform != 'win32'", - "python_full_version < '3.9' and sys_platform != 'win32'", + "python_full_version >= '3.13' and sys_platform != 'win32'", "python_full_version >= '3.9' and python_full_version < '3.13' and sys_platform == 'win32'", - "python_full_version < '3.9' and sys_platform == 'win32'", + "python_full_version >= '3.13' and sys_platform == 'win32'", ] dependencies = [ - { name = "pytest", marker = "python_full_version < '3.13'" }, + { name = "pytest", marker = "python_full_version >= '3.9'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/de/b4/0b378b7bf26a8ae161c3890c0b48a91a04106c5713ce81b4b080ea2f4f18/pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3", size = 46920 } +sdist = { url = "https://files.pythonhosted.org/packages/72/df/adcc0d60f1053d74717d21d58c0048479e9cab51464ce0d2965b086bd0e2/pytest_asyncio-0.25.2.tar.gz", hash = "sha256:3f8ef9a98f45948ea91a0ed3dc4268b5326c0e7bce73892acc654df4262ad45f", size = 53950 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/82/62e2d63639ecb0fbe8a7ee59ef0bc69a4669ec50f6d3459f74ad4e4189a2/pytest_asyncio-0.23.8-py3-none-any.whl", hash = "sha256:50265d892689a5faefb84df80819d1ecef566eb3549cf915dfb33569359d1ce2", size = 17663 }, + { url = "https://files.pythonhosted.org/packages/61/d8/defa05ae50dcd6019a95527200d3b3980043df5aa445d40cb0ef9f7f98ab/pytest_asyncio-0.25.2-py3-none-any.whl", hash = "sha256:0d0bb693f7b99da304a0634afc0a4b19e49d5e0de2d670f38dc4bfa5727c5075", size = 19400 }, ] [[package]] From 592ad1e312440ab4e3324916c41689ba7e731297 Mon Sep 17 00:00:00 2001 From: Cody Fincher Date: Wed, 8 Jan 2025 16:23:53 +0000 Subject: [PATCH 32/32] feat: explicitly pin min versions --- pyproject.toml | 3 ++- uv.lock | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 91493618fb..2c838f78f5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -175,7 +175,8 @@ linting = [ test = [ "covdefaults", "pytest", - "pytest-asyncio", + "pytest-asyncio<=0.24.0; python_version < \"3.9\"", + "pytest-asyncio>0.24.0; python_version >= \"3.9\"", "pytest-cov", "pytest-lazy-fixtures", "pytest-mock", diff --git a/uv.lock b/uv.lock index b912672024..db82c8796b 100644 --- a/uv.lock +++ b/uv.lock @@ -2049,7 +2049,8 @@ linting = [ test = [ { name = "covdefaults" }, { name = "pytest" }, - { name = "pytest-asyncio" }, + { name = "pytest-asyncio", marker = "python_full_version < '3.9'", specifier = "<=0.24.0" }, + { name = "pytest-asyncio", marker = "python_full_version >= '3.9'", specifier = ">0.24.0" }, { name = "pytest-cov" }, { name = "pytest-lazy-fixtures" }, { name = "pytest-mock" },