diff --git a/.all-contributorsrc b/.all-contributorsrc index 92d96040cc..9c601916b4 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -626,7 +626,8 @@ "contributions": [ "doc", "code", - "test" + "test", + "infra" ] }, { @@ -1724,6 +1725,72 @@ "contributions": [ "bug" ] + }, + { + "login": "tibor-reiss", + "name": "Tibor Reiss", + "avatar_url": "https://avatars.githubusercontent.com/u/75096465?v=4", + "profile": "https://github.com/tibor-reiss", + "contributions": [ + "test", + "doc", + "code" + ] + }, + { + "login": "0xE111", + "name": "Alex", + "avatar_url": "https://avatars.githubusercontent.com/u/11032969?v=4", + "profile": "https://pogrom.dev", + "contributions": [ + "bug", + "code" + ] + }, + { + "login": "JorenSix", + "name": "Joren Six", + "avatar_url": "https://avatars.githubusercontent.com/u/60453?v=4", + "profile": "http://0110.be", + "contributions": [ + "doc" + ] + }, + { + "login": "jderrien", + "name": "jderrien", + "avatar_url": "https://avatars.githubusercontent.com/u/145396?v=4", + "profile": "https://github.com/jderrien", + "contributions": [ + "doc" + ] + }, + { + "login": "PossiblePanda", + "name": "PossiblePanda", + "avatar_url": "https://avatars.githubusercontent.com/u/85448494?v=4", + "profile": "https://possiblepanda.me", + "contributions": [ + "doc" + ] + }, + { + "login": "evstratbg", + "name": "evstrat", + "avatar_url": "https://avatars.githubusercontent.com/u/10176401?v=4", + "profile": "https://github.com/evstratbg", + "contributions": [ + "infra" + ] + }, + { + "login": "eltociear", + "name": "Ikko Eltociear Ashimine", + "avatar_url": "https://avatars.githubusercontent.com/u/22633385?v=4", + "profile": "https://speakerdeck.com/eltociear", + "contributions": [ + "doc" + ] } ], "contributorsPerLine": 7, diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 16fa9aa56c..6eaa23c944 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -5,4 +5,4 @@ FROM python:${VARIANT} RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ && apt-get purge -y fish -RUN python3 -m pip install --upgrade setuptools cython pip poetry +RUN python3 -m pip install --upgrade setuptools cython pip pdm diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 32efc4f11f..7164363c93 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -2,7 +2,7 @@ "name": "litestar-org/litestar", "build": { "dockerfile": "./Dockerfile", - "context": ".", + "context": "." }, "features": { "ghcr.io/devcontainers/features/common-utils:2": { @@ -10,19 +10,19 @@ "username": "vscode", "userUid": "1000", "userGid": "1000", - "upgradePackages": "true", + "upgradePackages": "true" }, "ghcr.io/devcontainers/features/github-cli:1": {}, "ghcr.io/devcontainers-contrib/features/pre-commit:2": {}, "ghcr.io/devcontainers/features/python:1": "none", "ghcr.io/devcontainers/features/git:1": { "version": "latest", - "ppa": "false", - }, + "ppa": "false" + } }, "customizations": { "codespaces": { - "openFiles": ["CONTRIBUTING.rst"], + "openFiles": ["CONTRIBUTING.rst"] }, "vscode": { "extensions": [ @@ -31,7 +31,7 @@ "github.vscode-github-actions", "ms-python.black-formatter", "ms-python.mypy-type-checker", - "charliermarsh.ruff", + "charliermarsh.ruff" ], "settings": { "python.editor.defaultFormatter": "charliermarsh.ruff", @@ -45,26 +45,22 @@ "terminal.integrated.profiles.linux": { "bash": { "path": "bash", - "icon": "terminal-bash", + "icon": "terminal-bash" }, "zsh": { - "path": "zsh", + "path": "zsh" }, "fish": { - "path": "fish", - }, - }, - }, - }, + "path": "fish" + } + } + } + } }, "forwardPorts": [8000], "postCreateCommand": [ - "poetry", - "install", - "--extras", - "full", - "--with", - "docs,lint", + "pdm", + "install" ], - "remoteUser": "vscode", + "remoteUser": "vscode" } diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index cf06aeed9d..0993590675 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,4 +1,4 @@ -blank_lines_enabled: true +blank_issues_enabled: false contact_links: - name: Litestar Documentation url: https://docs.litestar.dev/ diff --git a/.github/workflows/docs-preview.yml b/.github/workflows/docs-preview.yml index c8be7035f9..38d7345aba 100644 --- a/.github/workflows/docs-preview.yml +++ b/.github/workflows/docs-preview.yml @@ -17,7 +17,7 @@ jobs: uses: actions/checkout@v4 - name: Download artifact - uses: dawidd6/action-download-artifact@v3 + uses: dawidd6/action-download-artifact@v6 with: workflow_conclusion: success run_id: ${{ github.event.workflow_run.id }} diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml index 7a01dc5126..4de1afb1a7 100644 --- a/.github/workflows/pr-labeler.yml +++ b/.github/workflows/pr-labeler.yml @@ -2,7 +2,6 @@ name: "Pull Request Labeler" on: pull_request_target: - pull_request: jobs: apply-labels: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9ec36aafe1..7612e9463a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,7 +24,7 @@ repos: - id: unasyncd additional_dependencies: ["ruff"] - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: "v0.3.5" + rev: "v0.4.4" hooks: - id: ruff args: ["--fix"] diff --git a/CITATION.cff b/CITATION.cff index 4bb104510b..d303bba056 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -3,12 +3,12 @@ title: Litestar message: 'If you use this software, please cite it as below.' type: software authors: - - Janek NouvertnΓ© - - Peter Schutt - - Cody Fincher - - Visakh Unnikrishnan - - Jacob Coffee - - Na'aman Hirschfeld + - given-names: Janek NouvertnΓ© + - given-names: Peter Schutt + - given-names: Cody Fincher + - given-names: Visakh Unnikrishnan + - given-names: Jacob Coffee + - given-names: Na'aman Hirschfeld repository-code: 'https://github.com/litestar-org/litestar' url: 'https://docs.litestar.dev/latest/' abstract: >- diff --git a/LICENSE b/LICENSE index 4e58f77a56..747085e1a7 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2021, 2022, 2023 Litestar Org. +Copyright (c) 2021, 2022, 2023, 2024 Litestar Org. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index f114e9ed07..d96743ac62 100644 --- a/README.md +++ b/README.md @@ -409,7 +409,7 @@ see [the contribution guide](CONTRIBUTING.rst). ste-pool
ste-pool

πŸ’» πŸš‡ - Alc-Alc
Alc-Alc

πŸ“– πŸ’» ⚠️ + Alc-Alc
Alc-Alc

πŸ“– πŸ’» ⚠️ πŸš‡ asomethings
asomethings

πŸ’» Garry Bullock
Garry Bullock

πŸ“– Niclas Haderer
Niclas Haderer

πŸ’» @@ -558,6 +558,15 @@ see [the contribution guide](CONTRIBUTING.rst). Carl Smedstad
Carl Smedstad

⚠️ Taein Min
Taein Min

πŸ“– Stanislav Lyu.
Stanislav Lyu.

πŸ› + Tibor Reiss
Tibor Reiss

⚠️ πŸ“– πŸ’» + Alex
Alex

πŸ› πŸ’» + + + Joren Six
Joren Six

πŸ“– + jderrien
jderrien

πŸ“– + PossiblePanda
PossiblePanda

πŸ“– + evstrat
evstrat

πŸš‡ + Ikko Eltociear Ashimine
Ikko Eltociear Ashimine

πŸ“– diff --git a/docs/conf.py b/docs/conf.py index 6358602ce3..cea9dcdb4c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -178,6 +178,7 @@ (PY_CLASS, "advanced_alchemy.extensions.litestar.config.SQLAlchemySyncConfig"), (PY_CLASS, "advanced_alchemy.extensions.litestar.config.SQLAlchemyAsyncConfig"), (PY_METH, "advanced_alchemy.extensions.litestar.plugins.SQLAlchemySerializationPlugin.create_dto_for_type"), + (PY_CLASS, "advanced_alchemy.base.BasicAttributes"), (PY_CLASS, "advanced_alchemy.config.AsyncSessionConfig"), (PY_CLASS, "advanced_alchemy.config.SyncSessionConfig"), (PY_CLASS, "advanced_alchemy.types.JsonB"), @@ -192,6 +193,7 @@ (PY_CLASS, "litestar.middleware.compression.gzip_facade.GzipCompression"), (PY_CLASS, "litestar.openapi.OpenAPIController"), (PY_CLASS, "openapi.controller.OpenAPIController"), + (PY_CLASS, "litestar.handlers.http_handlers.decorators._SubclassWarningMixin"), ] nitpick_ignore_regex = [ diff --git a/docs/examples/caching/__init__.py b/docs/examples/caching/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/examples/caching/cache.py b/docs/examples/caching/cache.py new file mode 100644 index 0000000000..deec9eaea1 --- /dev/null +++ b/docs/examples/caching/cache.py @@ -0,0 +1,22 @@ +from litestar import Litestar, get +from litestar.config.response_cache import CACHE_FOREVER + + +@get("/cached", cache=True) +async def my_cached_handler() -> str: + return "cached" + + +@get("/cached-seconds", cache=120) # seconds +async def my_cached_handler_seconds() -> str: + return "cached for 120 seconds" + + +@get("/cached-forever", cache=CACHE_FOREVER) +async def my_cached_handler_forever() -> str: + return "cached forever" + + +app = Litestar( + [my_cached_handler, my_cached_handler_seconds, my_cached_handler_forever], +) diff --git a/docs/examples/caching/key_builder.py b/docs/examples/caching/key_builder.py new file mode 100644 index 0000000000..762fb27476 --- /dev/null +++ b/docs/examples/caching/key_builder.py @@ -0,0 +1,9 @@ +from litestar import Litestar, Request +from litestar.config.response_cache import ResponseCacheConfig + + +def key_builder(request: Request) -> str: + return request.url.path + request.headers.get("my-header", "") + + +app = Litestar([], response_cache_config=ResponseCacheConfig(key_builder=key_builder)) diff --git a/docs/examples/caching/key_builder_for_route_handler.py b/docs/examples/caching/key_builder_for_route_handler.py new file mode 100644 index 0000000000..5b4c4d1cb2 --- /dev/null +++ b/docs/examples/caching/key_builder_for_route_handler.py @@ -0,0 +1,13 @@ +from litestar import Litestar, Request, get + + +def key_builder(request: Request) -> str: + return request.url.path + request.headers.get("my-header", "") + + +@get("/cached-path", cache=True, cache_key_builder=key_builder) +async def cached_handler() -> str: + return "cached" + + +app = Litestar([cached_handler]) diff --git a/docs/examples/caching/redis_store.py b/docs/examples/caching/redis_store.py new file mode 100644 index 0000000000..8832aab553 --- /dev/null +++ b/docs/examples/caching/redis_store.py @@ -0,0 +1,20 @@ +import asyncio + +from litestar import Litestar, get +from litestar.config.response_cache import ResponseCacheConfig +from litestar.stores.redis import RedisStore + + +@get(cache=10) +async def something() -> str: + await asyncio.sleep(1) + return "something" + + +redis_store = RedisStore.with_client(url="redis://localhost/", port=6379, db=0) +cache_config = ResponseCacheConfig(store="redis_backed_store") +app = Litestar( + [something], + stores={"redis_backed_store": redis_store}, + response_cache_config=cache_config, +) diff --git a/docs/examples/contrib/prometheus/using_prometheus_exporter.py b/docs/examples/contrib/prometheus/using_prometheus_exporter.py index bb63fb3a3e..99ed912be4 100644 --- a/docs/examples/contrib/prometheus/using_prometheus_exporter.py +++ b/docs/examples/contrib/prometheus/using_prometheus_exporter.py @@ -1,12 +1,13 @@ from litestar import Litestar from litestar.contrib.prometheus import PrometheusConfig, PrometheusController -# Default app name and prefix is litestar. -prometheus_config = PrometheusConfig() +def create_app(group_path: bool = False): + # Default app name and prefix is litestar. + prometheus_config = PrometheusConfig(group_path=group_path) -# By default the metrics are available in prometheus format and the path is set to '/metrics'. -# If you want to change the path and format you can do it by subclassing the PrometheusController class. + # By default the metrics are available in prometheus format and the path is set to '/metrics'. + # If you want to change the path and format you can do it by subclassing the PrometheusController class. -# Creating the litestar app instance with our custom PrometheusConfig and PrometheusController. -app = Litestar(route_handlers=[PrometheusController], middleware=[prometheus_config.middleware]) + # Creating the litestar app instance with our custom PrometheusConfig and PrometheusController. + return Litestar(route_handlers=[PrometheusController], middleware=[prometheus_config.middleware]) diff --git a/docs/examples/contrib/sqlalchemy/sqlalchemy_declarative_models.py b/docs/examples/contrib/sqlalchemy/sqlalchemy_declarative_models.py index 984181dc3a..cae8ff5ab1 100644 --- a/docs/examples/contrib/sqlalchemy/sqlalchemy_declarative_models.py +++ b/docs/examples/contrib/sqlalchemy/sqlalchemy_declarative_models.py @@ -1,16 +1,17 @@ +from __future__ import annotations + +import uuid from datetime import date -from typing import TYPE_CHECKING +from typing import List from uuid import UUID -from sqlalchemy import ForeignKey, select +from sqlalchemy import ForeignKey, func, select +from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession 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, SQLAlchemyInitPlugin - -if TYPE_CHECKING: - from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession +from litestar.contrib.sqlalchemy.plugins import AsyncSessionConfig, SQLAlchemyAsyncConfig, SQLAlchemyPlugin # the SQLAlchemy base includes a declarative model for you to use in your models. @@ -18,7 +19,7 @@ class Author(UUIDBase): name: Mapped[str] dob: Mapped[date] - books: Mapped[list["Book"]] = relationship(back_populates="author", lazy="selectin") + books: Mapped[List[Book]] = relationship(back_populates="author", lazy="selectin") # The `AuditBase` class includes the same UUID` based primary key (`id`) and 2 @@ -32,19 +33,24 @@ class Book(UUIDAuditBase): session_config = AsyncSessionConfig(expire_on_commit=False) sqlalchemy_config = SQLAlchemyAsyncConfig( - connection_string="sqlite+aiosqlite:///test.sqlite", session_config=session_config + connection_string="sqlite+aiosqlite:///test.sqlite", session_config=session_config, create_all=True ) # Create 'async_session' dependency. -sqlalchemy_plugin = SQLAlchemyInitPlugin(config=sqlalchemy_config) async def on_startup() -> None: - """Initializes the database.""" - async with sqlalchemy_config.get_engine().begin() as conn: - await conn.run_sync(UUIDBase.metadata.create_all) + """Adds some dummy data if no data is present.""" + async with sqlalchemy_config.get_session() as session: + statement = select(func.count()).select_from(Author) + count = await session.execute(statement) + if not count.scalar(): + author_id = uuid.uuid4() + session.add(Author(name="Stephen King", dob=date(1954, 9, 21), id=author_id)) + session.add(Book(title="It", author_id=author_id)) + await session.commit() @get(path="/authors") -async def get_authors(db_session: "AsyncSession", db_engine: "AsyncEngine") -> list[Author]: +async def get_authors(db_session: AsyncSession, db_engine: AsyncEngine) -> List[Author]: """Interact with SQLAlchemy engine and session.""" return list(await db_session.scalars(select(Author))) @@ -52,5 +58,6 @@ async def get_authors(db_session: "AsyncSession", db_engine: "AsyncEngine") -> l app = Litestar( route_handlers=[get_authors], on_startup=[on_startup], - plugins=[SQLAlchemyInitPlugin(config=sqlalchemy_config)], + debug=True, + plugins=[SQLAlchemyPlugin(config=sqlalchemy_config)], ) diff --git a/docs/examples/data_transfer_objects/factory/dto_data_problem_statement.py b/docs/examples/data_transfer_objects/factory/dto_data_problem_statement.py index f9ac24769f..411caa3cb6 100644 --- a/docs/examples/data_transfer_objects/factory/dto_data_problem_statement.py +++ b/docs/examples/data_transfer_objects/factory/dto_data_problem_statement.py @@ -1,20 +1,21 @@ from __future__ import annotations -from dataclasses import dataclass -from uuid import UUID +from dataclasses import dataclass, field +from uuid import UUID, uuid4 from litestar import Litestar, post from litestar.dto import DataclassDTO, DTOConfig @dataclass -class Person: - id: UUID +class User: name: str + email: str age: int + id: UUID = field(default_factory=uuid4) -class WriteDTO(DataclassDTO[Person]): +class UserWriteDTO(DataclassDTO[User]): """Don't allow client to set the id.""" config = DTOConfig(exclude={"id"}) @@ -23,12 +24,12 @@ class WriteDTO(DataclassDTO[Person]): # We need a dto for the handler to parse the request data per the configuration, however, # we don't need a return DTO as we are returning a dataclass, and Litestar already knows # how to serialize dataclasses. -@post("/person", dto=WriteDTO, return_dto=None, sync_to_thread=False) -def create_person(data: Person) -> Person: - """Create a person.""" +@post("/users", dto=UserWriteDTO, return_dto=None, sync_to_thread=False) +def create_user(data: User) -> User: + """Create an user.""" return data -app = Litestar(route_handlers=[create_person]) +app = Litestar(route_handlers=[create_user]) -# run: /person -H "Content-Type: application/json" -d '{"name":"Peter","age":41}' +# run: /users -H "Content-Type: application/json" -d '{"name":"Peter","email": "peter@example.com", "age":41}' diff --git a/docs/examples/data_transfer_objects/factory/dto_data_usage.py b/docs/examples/data_transfer_objects/factory/dto_data_usage.py index 3ebfcd90db..6b4db20325 100644 --- a/docs/examples/data_transfer_objects/factory/dto_data_usage.py +++ b/docs/examples/data_transfer_objects/factory/dto_data_usage.py @@ -1,5 +1,3 @@ -from __future__ import annotations - from dataclasses import dataclass from uuid import UUID, uuid4 @@ -8,24 +6,25 @@ @dataclass -class Person: - id: UUID +class User: name: str + email: str age: int + id: UUID -class WriteDTO(DataclassDTO[Person]): +class UserWriteDTO(DataclassDTO[User]): """Don't allow client to set the id.""" config = DTOConfig(exclude={"id"}) -@post("/person", dto=WriteDTO, return_dto=None, sync_to_thread=False) -def create_person(data: DTOData[Person]) -> Person: - """Create a person.""" +@post("/users", dto=UserWriteDTO, return_dto=None, sync_to_thread=False) +def create_user(data: DTOData[User]) -> User: + """Create an user.""" return data.create_instance(id=uuid4()) -app = Litestar(route_handlers=[create_person]) +app = Litestar(route_handlers=[create_user]) -# run: /person -H "Content-Type: application/json" -d '{"name":"Peter","age":41}' +# run: /users -H "Content-Type: application/json" -d '{"name":"Peter", "email": "peter@example.com", "age":41}' diff --git a/docs/examples/data_transfer_objects/factory/leading_underscore_private.py b/docs/examples/data_transfer_objects/factory/leading_underscore_private.py index ade7395fd6..70dfc6770f 100644 --- a/docs/examples/data_transfer_objects/factory/leading_underscore_private.py +++ b/docs/examples/data_transfer_objects/factory/leading_underscore_private.py @@ -6,8 +6,8 @@ @dataclass class Foo: - bar: str - _baz: str = "Mars" + this_will: str + _this_will: str = "Mars" @post("/", dto=DataclassDTO[Foo], sync_to_thread=False) @@ -17,4 +17,4 @@ def handler(data: Foo) -> Foo: app = Litestar(route_handlers=[handler]) -# run: / -H "Content-Type: application/json" -d '{"bar":"Hello","_baz":"World!"}' +# run: / -H "Content-Type: application/json" -d '{"bar":"stay","_baz":"go_away!"}' diff --git a/docs/examples/data_transfer_objects/factory/leading_underscore_private_override.py b/docs/examples/data_transfer_objects/factory/leading_underscore_private_override.py index 038d5b963d..f3705aaae3 100644 --- a/docs/examples/data_transfer_objects/factory/leading_underscore_private_override.py +++ b/docs/examples/data_transfer_objects/factory/leading_underscore_private_override.py @@ -6,8 +6,8 @@ @dataclass class Foo: - bar: str - _baz: str = "Mars" + this_will: str + _this_will: str = "not_go_away!" class DTO(DataclassDTO[Foo]): @@ -21,4 +21,4 @@ def handler(data: Foo) -> Foo: app = Litestar(route_handlers=[handler]) -# run: / -H "Content-Type: application/json" -d '{"bar":"Hello","_baz":"World!"}' +# run: / -H "Content-Type: application/json" -d '{"this_will":"stay","_this_will":"not_go_away!"}' diff --git a/docs/examples/data_transfer_objects/overriding_implicit_return_dto.py b/docs/examples/data_transfer_objects/overriding_implicit_return_dto.py index 9469626275..7c8f53fd40 100644 --- a/docs/examples/data_transfer_objects/overriding_implicit_return_dto.py +++ b/docs/examples/data_transfer_objects/overriding_implicit_return_dto.py @@ -1,8 +1,24 @@ -from litestar import post +from dataclasses import dataclass, field +from uuid import UUID, uuid4 -from .models import User, UserDTO +from litestar import Litestar, post +from litestar.dto import DataclassDTO -@post(dto=UserDTO, return_dto=None) +@dataclass +class User: + name: str + email: str + age: int + id: UUID = field(default_factory=uuid4) + + +UserDTO = DataclassDTO[User] + + +@post(dto=UserDTO, return_dto=None, sync_to_thread=False) def create_user(data: User) -> bytes: return data.name.encode(encoding="utf-8") + + +app = Litestar([create_user]) diff --git a/docs/examples/plugins/flash_messages/jinja.py b/docs/examples/plugins/flash_messages/jinja.py index 6772697642..27721ddfa5 100644 --- a/docs/examples/plugins/flash_messages/jinja.py +++ b/docs/examples/plugins/flash_messages/jinja.py @@ -1,9 +1,13 @@ from litestar import Litestar from litestar.contrib.jinja import JinjaTemplateEngine +from litestar.middleware.session.server_side import ServerSideSessionConfig from litestar.plugins.flash import FlashConfig, FlashPlugin from litestar.template.config import TemplateConfig template_config = TemplateConfig(engine=JinjaTemplateEngine, directory="templates") flash_plugin = FlashPlugin(config=FlashConfig(template_config=template_config)) -app = Litestar(plugins=[flash_plugin]) +app = Litestar( + plugins=[flash_plugin], + middleware=[ServerSideSessionConfig().middleware], +) diff --git a/docs/examples/plugins/flash_messages/mako.py b/docs/examples/plugins/flash_messages/mako.py index a5ce038eab..7bc9c637ec 100644 --- a/docs/examples/plugins/flash_messages/mako.py +++ b/docs/examples/plugins/flash_messages/mako.py @@ -1,9 +1,13 @@ from litestar import Litestar from litestar.contrib.mako import MakoTemplateEngine +from litestar.middleware.session.server_side import ServerSideSessionConfig from litestar.plugins.flash import FlashConfig, FlashPlugin from litestar.template.config import TemplateConfig template_config = TemplateConfig(engine=MakoTemplateEngine, directory="templates") flash_plugin = FlashPlugin(config=FlashConfig(template_config=template_config)) -app = Litestar(plugins=[flash_plugin]) +app = Litestar( + plugins=[flash_plugin], + middleware=[ServerSideSessionConfig().middleware], +) diff --git a/docs/examples/plugins/flash_messages/minijinja.py b/docs/examples/plugins/flash_messages/minijinja.py index 0ea2ce0f8e..26ae24be13 100644 --- a/docs/examples/plugins/flash_messages/minijinja.py +++ b/docs/examples/plugins/flash_messages/minijinja.py @@ -1,9 +1,13 @@ from litestar import Litestar from litestar.contrib.minijinja import MiniJinjaTemplateEngine +from litestar.middleware.session.server_side import ServerSideSessionConfig from litestar.plugins.flash import FlashConfig, FlashPlugin from litestar.template.config import TemplateConfig template_config = TemplateConfig(engine=MiniJinjaTemplateEngine, directory="templates") flash_plugin = FlashPlugin(config=FlashConfig(template_config=template_config)) -app = Litestar(plugins=[flash_plugin]) +app = Litestar( + plugins=[flash_plugin], + middleware=[ServerSideSessionConfig().middleware], +) diff --git a/docs/examples/plugins/flash_messages/usage.py b/docs/examples/plugins/flash_messages/usage.py index 914919ea0f..b9cfd71ab9 100644 --- a/docs/examples/plugins/flash_messages/usage.py +++ b/docs/examples/plugins/flash_messages/usage.py @@ -1,5 +1,6 @@ from litestar import Litestar, Request, get from litestar.contrib.jinja import JinjaTemplateEngine +from litestar.middleware.session.server_side import ServerSideSessionConfig from litestar.plugins.flash import FlashConfig, FlashPlugin, flash from litestar.response import Template from litestar.template.config import TemplateConfig @@ -23,4 +24,9 @@ async def index(request: Request) -> Template: ) -app = Litestar(plugins=[flash_plugin], route_handlers=[index], template_config=template_config) +app = Litestar( + plugins=[flash_plugin], + route_handlers=[index], + template_config=template_config, + middleware=[ServerSideSessionConfig().middleware], +) diff --git a/docs/examples/responses/sse_responses.py b/docs/examples/responses/sse_responses.py index 43b71df0f5..ee97379757 100644 --- a/docs/examples/responses/sse_responses.py +++ b/docs/examples/responses/sse_responses.py @@ -2,15 +2,28 @@ from typing import AsyncGenerator from litestar import Litestar, get -from litestar.response import ServerSentEvent +from litestar.response import ServerSentEvent, ServerSentEventMessage +from litestar.types import SSEData -async def my_generator() -> AsyncGenerator[bytes, None]: +async def my_generator() -> AsyncGenerator[SSEData, None]: count = 0 while count < 10: await sleep(0.01) count += 1 + # In the generator you can yield integers, strings, bytes, dictionaries, or ServerSentEventMessage objects + # dicts can have the following keys: data, event, id, retry, comment + + # here we yield an integer + yield count + # here a string yield str(count) + # here bytes + yield str(count).encode("utf-8") + # here a dictionary + yield {"data": 2 * count, "event": "event2", "retry": 10} + # here a ServerSentEventMessage object + yield ServerSentEventMessage(event="something-with-comment", retry=1000, comment="some comment") @get(path="/count", sync_to_thread=False) diff --git a/docs/reference/concurrency.rst b/docs/reference/concurrency.rst index 89bf990a58..61203f6641 100644 --- a/docs/reference/concurrency.rst +++ b/docs/reference/concurrency.rst @@ -1,5 +1,5 @@ -cli -=== +concurrency +=========== .. automodule:: litestar.concurrency :members: diff --git a/docs/release-notes/2.x-changelog.rst b/docs/release-notes/2.x-changelog.rst index a9792f931e..3bd45b7a6c 100644 --- a/docs/release-notes/2.x-changelog.rst +++ b/docs/release-notes/2.x-changelog.rst @@ -3057,7 +3057,7 @@ :pr: 1647 Dependencies can now be used in the - :class:`~litestar.handlers.websocket_listener` hooks + :func:`~litestar.handlers.websocket_listener` hooks ``on_accept``, ``on_disconnect`` and the ``connection_lifespan`` context manager. The ``socket`` parameter is therefore also not mandatory anymore in those callables. @@ -3208,7 +3208,7 @@ :issue: 1615 A bug was fixed that would cause a type error when using a - :class:`websocket_listener ` + :func:`websocket_listener ` in a ``Controller`` .. change:: Add ``connection_accept_handler`` to ``websocket_listener`` @@ -3217,7 +3217,7 @@ :issue: 1571 Add a new ``connection_accept_handler`` parameter to - :class:`websocket_listener `, + :func:`websocket_listener `, which can be used to customize how a connection is accepted, for example to add headers or subprotocols @@ -3305,7 +3305,7 @@ appropriate event hooks - to use a context manager. The ``connection_lifespan`` argument was added to the - :class:`WebSocketListener `, which accepts + :func:`WebSocketListener `, which accepts an asynchronous context manager, which can be used to handle the lifespan of the socket. @@ -3419,7 +3419,7 @@ :pr: 1518 Support for DTOs has been added to :class:`WebSocketListener ` and - :class:`WebSocketListener `. A ``dto`` and ``return_dto`` parameter has + :func:`WebSocketListener `. A ``dto`` and ``return_dto`` parameter has been added, providing the same functionality as their route handler counterparts. .. change:: DTO based serialization plugin diff --git a/docs/release-notes/whats-new-3.rst b/docs/release-notes/whats-new-3.rst index 29c055a31b..c79a754ba1 100644 --- a/docs/release-notes/whats-new-3.rst +++ b/docs/release-notes/whats-new-3.rst @@ -150,3 +150,37 @@ Removal of deprecated ``litestar.middleware.exceptions`` module and ``ExceptionH The deprecated ``litestar.middleware.exceptions`` module and the ``ExceptionHandlerMiddleware`` have been removed. Since ``ExceptionHandlerMiddleware`` has been applied automatically behind the scenes if necessary, no action is required. +Removal of semantic HTTP route handler classes +----------------------------------------------- + +The semantic ``HTTPRouteHandler`` classes have been removed in favour of functional +decorators. ``route``, ``get``, ``post``, ``patch``, ``put``, ``head`` and ``delete`` +are now all decorator functions returning :class:`~.handlers.HTTPRouteHandler` +instances. + +As a result, customizing the decorators directly is not possible anymore. Instead, to +use a route handler decorator with a custom route handler class, the ``handler_class`` +parameter to the decorator function can be used: + +Before: + +.. code-block:: python + + class my_get_handler(get): + ... # custom handler + + @my_get_handler() + async def handler() -> Any: + ... + +After: + +.. code-block:: python + + class MyHTTPRouteHandler(HTTPRouteHandler): + ... # custom handler + + + @get(handler_class=MyHTTPRouteHandler) + async def handler() -> Any: + ... diff --git a/docs/usage/caching.rst b/docs/usage/caching.rst index d3c26bdc12..3eaf68cb8e 100644 --- a/docs/usage/caching.rst +++ b/docs/usage/caching.rst @@ -7,13 +7,9 @@ Caching responses Sometimes it's desirable to cache some responses, especially if these involve expensive calculations, or when polling is expected. Litestar comes with a simple mechanism for caching: -.. code-block:: python - - from litestar import get - - - @get("/cached-path", cache=True) - def my_cached_handler() -> str: ... +.. literalinclude:: /examples/caching/cache.py + :language: python + :lines: 1, 4-8 By setting :paramref:`~litestar.handlers.HTTPRouteHandler.cache` to ``True``, the response from the handler will be cached. If no ``cache_key_builder`` is set in the route handler, caching for the route handler will be @@ -25,31 +21,22 @@ enabled for the :attr:`~.config.response_cache.ResponseCacheConfig.default_expir Alternatively you can specify the number of seconds to cache the responses from the given handler like so: -.. code-block:: python +.. literalinclude:: /examples/caching/cache.py + :language: python :caption: Caching the response for 120 seconds by setting the :paramref:`~litestar.handlers.HTTPRouteHandler.cache` parameter to the number of seconds to cache the response. + :lines: 1, 9-13 :emphasize-lines: 4 - from litestar import get - - - @get("/cached-path", cache=120) # seconds - def my_cached_handler() -> str: ... - - If you want the response to be cached indefinitely, you can pass the :class:`~.config.response_cache.CACHE_FOREVER` sentinel instead: -.. code-block:: python +.. literalinclude:: /examples/caching/cache.py + :language: python :caption: Caching the response indefinitely by setting the :paramref:`~litestar.handlers.HTTPRouteHandler.cache` parameter to :class:`~litestar.config.response_cache.CACHE_FOREVER`. - - from litestar import get - from litestar.config.response_cache import CACHE_FOREVER - - - @get("/cached-path", cache=CACHE_FOREVER) - def my_cached_handler() -> str: ... + :lines: 1, 3, 14-18 + :emphasize-lines: 5 Configuration ------------- @@ -63,45 +50,20 @@ Changing where data is stored By default, caching will use the :class:`~.stores.memory.MemoryStore`, but it can be configured with any :class:`~.stores.base.Store`, for example :class:`~.stores.redis.RedisStore`: -.. code-block:: python +.. literalinclude:: /examples/caching/redis_store.py + :language: python :caption: Using Redis as the cache store. - from litestar.config.cache import ResponseCacheConfig - from litestar.stores.redis import RedisStore - - redis_store = RedisStore(url="redis://localhost/", port=6379, db=0) - - cache_config = ResponseCacheConfig(store=redis_store) - - Specifying a cache key builder ++++++++++++++++++++++++++++++ Litestar uses the request's path + sorted query parameters as the cache key. This can be adjusted by providing a "key builder" function, either at application or route handler level. -.. code-block:: python +.. literalinclude:: /examples/caching/key_builder.py + :language: python :caption: Using a custom cache key builder. - from litestar import Litestar, Request - from litestar.config.cache import ResponseCacheConfig - - - def key_builder(request: Request) -> str: - return request.url.path + request.headers.get("my-header", "") - - - app = Litestar([], cache_config=ResponseCacheConfig(key_builder=key_builder)) - -.. code-block:: python +.. literalinclude:: /examples/caching/key_builder_for_route_handler.py + :language: python :caption: Using a custom cache key builder for a specific route handler. - - from litestar import Litestar, Request, get - - - def key_builder(request: Request) -> str: - return request.url.path + request.headers.get("my-header", "") - - - @get("/cached-path", cache=True, cache_key_builder=key_builder) - def cached_handler() -> str: ... diff --git a/docs/usage/dependency-injection.rst b/docs/usage/dependency-injection.rst index 114bf3fa91..b709ed326b 100644 --- a/docs/usage/dependency-injection.rst +++ b/docs/usage/dependency-injection.rst @@ -53,6 +53,14 @@ the application: The above example illustrates how dependencies are declared on the different layers of the application. +.. note:: + + Litestar needs the injected types at runtime which might clash with linter rules' recommendation to use ``TYPE_CHECKING``. + + .. seealso:: + + :ref:`Signature namespace ` + Dependencies can be either callables - sync or async functions, methods, or class instances that implement the :meth:`object.__call__` method, or classes. These are in turn wrapped inside an instance of the :class:`Provide <.di.Provide>` class. diff --git a/docs/usage/dto/1-abstract-dto.rst b/docs/usage/dto/1-abstract-dto.rst index 346141a573..4ae382d9b5 100644 --- a/docs/usage/dto/1-abstract-dto.rst +++ b/docs/usage/dto/1-abstract-dto.rst @@ -176,7 +176,7 @@ handler. .. literalinclude:: /examples/data_transfer_objects/factory/dto_data_problem_statement.py :language: python - :emphasize-lines: 18,19,20,21,27 + :emphasize-lines: 19,20,21,22,28 :linenos: Notice that we get a ``500`` response from the handler - this is because the DTO has attempted to convert the request diff --git a/docs/usage/logging.rst b/docs/usage/logging.rst index c39861aea4..85269282c6 100644 --- a/docs/usage/logging.rst +++ b/docs/usage/logging.rst @@ -18,7 +18,7 @@ Application and request level loggers can be configured using the :class:`~lites logging_config = LoggingConfig( - root={"level": logging.getLevelName(logging.INFO), "handlers": ["console"]}, + root={"level": "INFO", "handlers": ["queue_listener"]}, formatters={ "standard": {"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"} }, diff --git a/docs/usage/openapi/ui_plugins.rst b/docs/usage/openapi/ui_plugins.rst index a557f076b9..df10675c57 100644 --- a/docs/usage/openapi/ui_plugins.rst +++ b/docs/usage/openapi/ui_plugins.rst @@ -87,7 +87,7 @@ All plugins support: Most plugins support the following additional options: - ``version``: The version of the UIs JS and (in some cases) CSS bundle to use. We use the ``version`` to construct the - URL to retrieve the the bundle from ``unpkg``, e.g., ``https://unpkg.com/rapidoc@/dist/rapidoc-min.js`` + URL to retrieve the bundle from ``unpkg``, e.g., ``https://unpkg.com/rapidoc@/dist/rapidoc-min.js`` - ``js_url``: The URL to the JS bundle. If provided, this will override the ``version`` option. - ``css_url``: The URL to the CSS bundle. If provided, this will override the ``version`` option. diff --git a/docs/usage/plugins/flash_messages.rst b/docs/usage/plugins/flash_messages.rst index 8ff46b8db6..40e61554bc 100644 --- a/docs/usage/plugins/flash_messages.rst +++ b/docs/usage/plugins/flash_messages.rst @@ -57,6 +57,7 @@ Breakdown +++++++++ #. Here we import the requires classes and functions from the Litestar package and related plugins. +#. Flash messages requires a valid session configuration, so we create and enable the ``ServerSideSession`` middleware. #. We then create our ``TemplateConfig`` and ``FlashConfig`` instances, each setting up the configuration for the template engine and flash messages, respectively. #. A single route handler named ``index`` is defined using the ``@get()`` decorator. diff --git a/docs/usage/routing/handlers.rst b/docs/usage/routing/handlers.rst index 78fc9f6e7d..289ef81e7b 100644 --- a/docs/usage/routing/handlers.rst +++ b/docs/usage/routing/handlers.rst @@ -7,7 +7,7 @@ handler :term:`decorators ` exported from Litestar. For example: .. code-block:: python - :caption: Defining a route handler by decorating a function with the :class:`@get() <.handlers.get>` :term:`decorator` + :caption: Defining a route handler by decorating a function with the :func:`@get() <.handlers.get>` :term:`decorator` from litestar import get @@ -146,12 +146,11 @@ There are several reasons for why this limitation is enforced: HTTP route handlers ------------------- -The most commonly used route handlers are those that handle HTTP requests and responses. -These route handlers all inherit from the :class:`~.handlers.HTTPRouteHandler` class, which is aliased as the -:term:`decorator` called :func:`~.handlers.route`: +The :class:`~.handlers.HTTPRouteHandler` is used to handle HTTP requests, and can be +created with the :func:`~.handlers.route` :term:`decorator`: .. code-block:: python - :caption: Defining a route handler by decorating a function with the :class:`@route() <.handlers.route>` + :caption: Defining a route handler by decorating a function with the :func:`@route() <.handlers.route>` :term:`decorator` from litestar import HttpMethod, route @@ -160,20 +159,24 @@ These route handlers all inherit from the :class:`~.handlers.HTTPRouteHandler` c @route(path="/some-path", http_method=[HttpMethod.GET, HttpMethod.POST]) async def my_endpoint() -> None: ... -As mentioned above, :func:`@route() <.handlers.route>` is merely an alias for ``HTTPRouteHandler``, -thus the below code is equivalent to the one above: +The same can be achieved without a decorator, by using ``HTTPRouteHandler`` directly: .. code-block:: python - :caption: Defining a route handler by decorating a function with the - :class:`HTTPRouteHandler <.handlers.HTTPRouteHandler>` class + :caption: Defining a route handler creating an instance of + :class:`HTTPRouteHandler <.handlers.HTTPRouteHandler>` from litestar import HttpMethod from litestar.handlers.http_handlers import HTTPRouteHandler - @HTTPRouteHandler(path="/some-path", http_method=[HttpMethod.GET, HttpMethod.POST]) async def my_endpoint() -> None: ... + handler = HTTPRouteHandler( + path="/some-path", + http_method=[HttpMethod.GET, HttpMethod.POST], + fn=my_endpoint + ) + Semantic handler :term:`decorators ` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -189,8 +192,8 @@ which correlates with their name: * :func:`@post() <.handlers.post>` * :func:`@put() <.handlers.put>` -These are used exactly like :func:`@route() <.handlers.route>` with the sole exception that you cannot configure the -:paramref:`~.handlers.HTTPRouteHandler.http_method` :term:`kwarg `: +These are used exactly like :func:`@route() <.handlers.route>` with the sole exception that you don't need to configure +the :paramref:`~.handlers.HTTPRouteHandler.http_method` :term:`kwarg `: .. dropdown:: Click to see the predefined route handlers @@ -240,11 +243,6 @@ These are used exactly like :func:`@route() <.handlers.route>` with the sole exc @delete(path="/resources/{pk:int}") async def delete_resource(pk: int) -> None: ... -Although these :term:`decorators ` are merely subclasses of :class:`~.handlers.HTTPRouteHandler` that pre-set -the :paramref:`~.handlers.HTTPRouteHandler.http_method`, using :func:`@get() <.handlers.get>`, -:func:`@patch() <.handlers.patch>`, :func:`@put() <.handlers.put>`, :func:`@delete() <.handlers.delete>`, or -:func:`@post() <.handlers.post>` instead of :func:`@route() <.handlers.route>` makes the code clearer and simpler. - Furthermore, in the OpenAPI specification each unique combination of HTTP verb (e.g. ``GET``, ``POST``, etc.) and path is regarded as a distinct `operation `_\ , and each operation should be distinguished by a unique :paramref:`~.handlers.HTTPRouteHandler.operation_id` and optimally @@ -277,8 +275,8 @@ A WebSocket connection can be handled with a :func:`@websocket() <.handlers.Webs await socket.send_json({...}) await socket.close() -The :func:`@websocket() <.handlers.WebsocketRouteHandler>` :term:`decorator` is an alias of the -:class:`~.handlers.WebsocketRouteHandler` class. Thus, the below code is equivalent to the one above: +The :func:`@websocket() <.handlers.WebsocketRouteHandler>` :term:`decorator` can be used to create an instance of +:class:`~.handlers.WebsocketRouteHandler`. Therefore, the below code is equivalent to the one above: .. code-block:: python :caption: Using the :class:`~.handlers.WebsocketRouteHandler` class directly @@ -286,13 +284,16 @@ The :func:`@websocket() <.handlers.WebsocketRouteHandler>` :term:`decorator` is from litestar import WebSocket from litestar.handlers.websocket_handlers import WebsocketRouteHandler - - @WebsocketRouteHandler(path="/socket") async def my_websocket_handler(socket: WebSocket) -> None: await socket.accept() await socket.send_json({...}) await socket.close() + my_websocket_handler = WebsocketRouteHandler( + path="/socket", + fn=my_websocket_handler, + ) + In difference to HTTP routes handlers, websocket handlers have the following requirements: #. They **must** declare a ``socket`` :term:`kwarg `. @@ -332,8 +333,8 @@ If you need to write your own ASGI application, you can do so using the :func:`@ ) await response(scope=scope, receive=receive, send=send) -Like other route handlers, the :func:`@asgi() <.handlers.asgi>` :term:`decorator` is an alias of the -:class:`~.handlers.ASGIRouteHandler` class. Thus, the code below is equivalent to the one above: +:func:`@asgi() <.handlers.asgi>` :term:`decorator` can be used to create an instance of +:class:`~.handlers.ASGIRouteHandler`. Therefore, the code below is equivalent to the one above: .. code-block:: python :caption: Using the :class:`~.handlers.ASGIRouteHandler` class directly @@ -343,8 +344,6 @@ Like other route handlers, the :func:`@asgi() <.handlers.asgi>` :term:`decorator from litestar.status_codes import HTTP_400_BAD_REQUEST from litestar.types import Scope, Receive, Send - - @ASGIRouteHandler(path="/my-asgi-app") async def my_asgi_app(scope: Scope, receive: Receive, send: Send) -> None: if scope["type"] == "http": if scope["method"] == "GET": @@ -356,7 +355,10 @@ Like other route handlers, the :func:`@asgi() <.handlers.asgi>` :term:`decorator ) await response(scope=scope, receive=receive, send=send) -Limitations of ASGI route handlers + my_asgi_app = ASGIRouteHandler(path="/my-asgi-app", fn=my_asgi_app) + + +ASGI route handler considerations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In difference to the other route handlers, the :func:`@asgi() <.handlers.asgi>` route handler accepts only three diff --git a/docs/usage/routing/overview.rst b/docs/usage/routing/overview.rst index a2494b6aea..e462ded15d 100644 --- a/docs/usage/routing/overview.rst +++ b/docs/usage/routing/overview.rst @@ -260,7 +260,7 @@ The handler function will receive all requests with an url that begins with ``/s :class: info If we are sending a request to the above with the url ``/some/sub-path``, the handler will be invoked and - the value of ``scope["path"]`` will equal ``"/`"``. If we send a request to ``/some/sub-path/abc``, it will also be + the value of ``scope["path"]`` will equal ``"/"``. If we send a request to ``/some/sub-path/abc``, it will also be invoked,and ``scope["path"]`` will equal ``"/abc"``. Mounting is especially useful when you need to combine components of other ASGI applications - for example, for third diff --git a/docs/usage/security/abstract-authentication-middleware.rst b/docs/usage/security/abstract-authentication-middleware.rst index 916bfb909f..9db3ac6c77 100644 --- a/docs/usage/security/abstract-authentication-middleware.rst +++ b/docs/usage/security/abstract-authentication-middleware.rst @@ -52,15 +52,14 @@ example here let us say it is a `SQLAlchemy `_ mod import uuid - from sqlalchemy import Column from sqlalchemy.dialects.postgresql import UUID - from sqlalchemy.orm import declarative_base + from sqlalchemy.orm import declarative_base, mapped_column, Mapped Base = declarative_base() class User(Base): - id: uuid.UUID | None = Column( + id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), default=uuid.uuid4, primary_key=True ) # ... other fields follow, but we only require id for this example diff --git a/docs/usage/websockets.rst b/docs/usage/websockets.rst index dad724587b..9aa2c8ebd9 100644 --- a/docs/usage/websockets.rst +++ b/docs/usage/websockets.rst @@ -8,7 +8,7 @@ exceptions, and parsing incoming and serializing outgoing data. In addition to t low-level :class:`WebSocket route handler <.handlers.websocket>`, Litestar offers two high level interfaces: -- :class:`websocket_listener <.handlers.websocket_listener>` +- :func:`websocket_listener <.handlers.websocket_listener>` - :class:`WebSocketListener <.handlers.WebsocketListener>` @@ -38,7 +38,7 @@ type of data which should be received, and it will be converted accordingly. .. note:: Contrary to WebSocket route handlers, functions decorated with - :class:`websocket_listener <.handlers.websocket_listener>` don't have to be + :func:`websocket_listener <.handlers.websocket_listener>` don't have to be asynchronous. diff --git a/litestar/_asgi/routing_trie/traversal.py b/litestar/_asgi/routing_trie/traversal.py index 6e5b3c05cc..8c5bdf4453 100644 --- a/litestar/_asgi/routing_trie/traversal.py +++ b/litestar/_asgi/routing_trie/traversal.py @@ -136,8 +136,8 @@ def parse_path_to_route( asgi_app, handler = parse_node_handlers(node=root_node.children[path], method=method) return asgi_app, handler, path, {} - if mount_paths_regex and (match := mount_paths_regex.search(path)): - mount_path = path[match.start() : match.end()] + if mount_paths_regex and (match := mount_paths_regex.match(path)): + mount_path = path[: match.end()] mount_node = mount_routes[mount_path] remaining_path = path[match.end() :] # since we allow regular handlers under static paths, we must validate that the request does not match diff --git a/litestar/_kwargs/extractors.py b/litestar/_kwargs/extractors.py index e3b347eadb..20f6a46cee 100644 --- a/litestar/_kwargs/extractors.py +++ b/litestar/_kwargs/extractors.py @@ -16,7 +16,8 @@ from litestar.exceptions import ValidationException from litestar.params import BodyKwarg from litestar.types import Empty -from litestar.utils.predicates import is_non_string_sequence +from litestar.utils import make_non_optional_union +from litestar.utils.predicates import is_non_string_sequence, is_optional_union from litestar.utils.scope.state import ScopeState if TYPE_CHECKING: @@ -348,7 +349,10 @@ async def _extract_multipart( if field_definition.is_non_string_sequence: values = list(form_values.values()) - if field_definition.has_inner_subclass_of(UploadFile) and isinstance(values[0], list): + if isinstance(values[0], list) and ( + field_definition.has_inner_subclass_of(UploadFile) + or (field_definition.is_optional and field_definition.inner_types[0].is_non_string_sequence) + ): return values[0] return values @@ -364,7 +368,14 @@ async def _extract_multipart( for name, tp in field_definition.get_type_hints().items(): value = form_values.get(name) - if value is not None and is_non_string_sequence(tp) and not isinstance(value, list): + if ( + value is not None + and not isinstance(value, list) + and ( + is_non_string_sequence(tp) + or (is_optional_union(tp) and is_non_string_sequence(make_non_optional_union(tp))) + ) + ): form_values[name] = [value] return form_values diff --git a/litestar/_signature/model.py b/litestar/_signature/model.py index 42c79947f5..33653ed548 100644 --- a/litestar/_signature/model.py +++ b/litestar/_signature/model.py @@ -38,7 +38,7 @@ from litestar.exceptions import InternalServerException, ValidationException from litestar.params import KwargDefinition, ParameterKwarg from litestar.typing import FieldDefinition # noqa -from litestar.utils import is_class_and_subclass +from litestar.utils import get_origin_or_inner_type, is_class_and_subclass from litestar.utils.dataclass import simple_asdict if TYPE_CHECKING: @@ -85,8 +85,15 @@ def _deserializer(target_type: Any, value: Any, default_deserializer: Callable[[ if isinstance(value, DTOData): return value - if isinstance(value, target_type): - return value + try: + if isinstance(value, target_type): + return value + except TypeError as exc: + if (origin := get_origin_or_inner_type(target_type)) is not None: + if isinstance(value, origin): + return value + else: + raise exc if decoder := getattr(target_type, "_decoder", None): return decoder(target_type, value) diff --git a/litestar/channels/plugin.py b/litestar/channels/plugin.py index 59884454d4..5bdac7f9c5 100644 --- a/litestar/channels/plugin.py +++ b/litestar/channels/plugin.py @@ -116,11 +116,11 @@ def on_app_init(self, app_config: AppConfig) -> AppConfig: if self._create_route_handlers: if self._arbitrary_channels_allowed: path = self._handler_root_path + "{channel_name:str}" - route_handlers = [WebsocketRouteHandler(path)(self._ws_handler_func)] + route_handlers = [WebsocketRouteHandler(path, fn=self._ws_handler_func)] else: route_handlers = [ - WebsocketRouteHandler(self._handler_root_path + channel_name)( - self._create_ws_handler_func(channel_name) + WebsocketRouteHandler( + self._handler_root_path + channel_name, fn=self._create_ws_handler_func(channel_name) ) for channel_name in self._channels ] diff --git a/litestar/cli/__init__.py b/litestar/cli/__init__.py index 7dabefaae9..a167be38fa 100644 --- a/litestar/cli/__init__.py +++ b/litestar/cli/__init__.py @@ -7,7 +7,11 @@ # Ensure `rich_click` patching occurs before we do any imports from `click`. if find_spec("rich_click") is not None: # pragma: no cover import rich_click as click - from rich_click.cli import patch as rich_click_patch + + try: + from rich_click.patch import patch as rich_click_patch + except ImportError: + from rich_click.cli import patch as rich_click_patch rich_click_patch() click.rich_click.USE_RICH_MARKUP = True diff --git a/litestar/cli/_utils.py b/litestar/cli/_utils.py index 91c6daa1dd..1c43c5b94d 100644 --- a/litestar/cli/_utils.py +++ b/litestar/cli/_utils.py @@ -252,8 +252,8 @@ def wrapped(ctx: Context, /, *args: P.args, **kwargs: P.kwargs) -> T: def _wrap_commands(commands: Iterable[Command]) -> None: for command in commands: - if isinstance(command, Group): - _wrap_commands(command.commands.values()) + if hasattr(command, "commands"): + _wrap_commands(command.commands.values()) # pyright: ignore[reportGeneralTypeIssues] elif command.callback: command.callback = _inject_args(command.callback) diff --git a/litestar/config/csrf.py b/litestar/config/csrf.py index 5094a5b7c8..225dc23daa 100644 --- a/litestar/config/csrf.py +++ b/litestar/config/csrf.py @@ -34,7 +34,7 @@ class CSRFConfig: """The value to set in the ``SameSite`` attribute of the cookie.""" cookie_domain: str | None = field(default=None) """Specifies which hosts can receive the cookie.""" - safe_methods: set[Method] = field(default_factory=lambda: {"GET", "HEAD"}) + safe_methods: set[Method] = field(default_factory=lambda: {"GET", "HEAD", "OPTIONS"}) """A set of "safe methods" that can set the cookie.""" exclude: str | list[str] | None = field(default=None) """A pattern or list of patterns to skip in the CSRF middleware.""" diff --git a/litestar/contrib/jinja.py b/litestar/contrib/jinja.py index c5aec0a177..c0ceacb3aa 100644 --- a/litestar/contrib/jinja.py +++ b/litestar/contrib/jinja.py @@ -44,7 +44,6 @@ def __init__( engine_instance: A jinja Environment instance. """ - super().__init__(directory, engine_instance) if directory and engine_instance: raise ImproperlyConfiguredException("You must provide either a directory or a jinja2 Environment instance.") if directory: diff --git a/litestar/contrib/mako.py b/litestar/contrib/mako.py index b8a3a73e10..44cc8633c2 100644 --- a/litestar/contrib/mako.py +++ b/litestar/contrib/mako.py @@ -71,7 +71,6 @@ def __init__(self, directory: Path | list[Path] | None = None, engine_instance: directory: Direct path or list of directory paths from which to serve templates. engine_instance: A mako TemplateLookup instance. """ - super().__init__(directory, engine_instance) if directory and engine_instance: raise ImproperlyConfiguredException("You must provide either a directory or a mako TemplateLookup.") if directory: diff --git a/litestar/contrib/minijinja.py b/litestar/contrib/minijinja.py index bf61842ce2..9d556ef75b 100644 --- a/litestar/contrib/minijinja.py +++ b/litestar/contrib/minijinja.py @@ -104,7 +104,6 @@ def __init__(self, directory: Path | list[Path] | None = None, engine_instance: directory: Direct path or list of directory paths from which to serve templates. engine_instance: A Minijinja Environment instance. """ - super().__init__(directory, engine_instance) if directory and engine_instance: raise ImproperlyConfiguredException( "You must provide either a directory or a minijinja Environment instance." diff --git a/litestar/contrib/prometheus/config.py b/litestar/contrib/prometheus/config.py index b77dab0ecb..6b0ceb6409 100644 --- a/litestar/contrib/prometheus/config.py +++ b/litestar/contrib/prometheus/config.py @@ -50,6 +50,9 @@ class PrometheusConfig: middleware_class: type[PrometheusMiddleware] = field(default=PrometheusMiddleware) """The middleware class to use. """ + group_path: bool = field(default=False) + """Whether to group paths in the metrics to avoid cardinality explosion. + """ @property def middleware(self) -> DefineMiddleware: diff --git a/litestar/contrib/prometheus/middleware.py b/litestar/contrib/prometheus/middleware.py index 50bc7cb03f..77cc09315e 100644 --- a/litestar/contrib/prometheus/middleware.py +++ b/litestar/contrib/prometheus/middleware.py @@ -115,9 +115,20 @@ def _get_default_labels(self, request: Request[Any, Any, Any]) -> dict[str, str A dictionary of default labels. """ + path = request.url.path + if self._config.group_path: + path_parts = path.split("/")[1:] + path = "" + for path_parameter, path_parameter_value in request.scope.get("path_params", {}).items(): + for path_part in path_parts: + path_parameter_value = str(path_parameter_value) + if path_part == path_parameter_value: + path += f"/{{{path_parameter}}}" + else: + path += f"/{path_part}" return { "method": request.method if request.scope["type"] == ScopeType.HTTP else request.scope["type"], - "path": request.url.path, + "path": path, "status_code": 200, "app_name": self._config.app_name, } diff --git a/litestar/contrib/pydantic/pydantic_schema_plugin.py b/litestar/contrib/pydantic/pydantic_schema_plugin.py index 5d7045e4f7..2eda65f2fc 100644 --- a/litestar/contrib/pydantic/pydantic_schema_plugin.py +++ b/litestar/contrib/pydantic/pydantic_schema_plugin.py @@ -286,7 +286,8 @@ def for_pydantic_model(cls, field_definition: FieldDefinition, schema_creator: S else: # pydantic v1 requires some workarounds here model_annotations = { - k: f.outer_type_ if f.required else Optional[f.outer_type_] for k, f in model.__fields__.items() + k: f.outer_type_ if f.required or f.default else Optional[f.outer_type_] + for k, f in model.__fields__.items() } if is_generic_model: diff --git a/litestar/controller.py b/litestar/controller.py index 967454b168..b045bfc7e6 100644 --- a/litestar/controller.py +++ b/litestar/controller.py @@ -12,7 +12,7 @@ from litestar.handlers.http_handlers import HTTPRouteHandler from litestar.handlers.websocket_handlers import WebsocketRouteHandler from litestar.types.empty import Empty -from litestar.utils import ensure_async_callable, normalize_path +from litestar.utils import normalize_path from litestar.utils.signature import add_types_to_signature_namespace __all__ = ("Controller",) @@ -51,6 +51,7 @@ class Controller: "after_request", "after_response", "before_request", + "cache_control", "dependencies", "dto", "etag", @@ -69,6 +70,7 @@ class Controller: "return_dto", "security", "signature_namespace", + "signature_types", "tags", "type_encoders", "type_decoders", @@ -174,12 +176,11 @@ def __init__(self, owner: Router) -> None: Args: owner: An instance of :class:`Router <.router.Router>` """ - # Since functions set on classes are bound, we need replace the bound instance with the class version and wrap - # it to ensure it does not get bound. + # Since functions set on classes are bound, we need replace the bound instance with the class version for key in ("after_request", "after_response", "before_request"): cls_value = getattr(type(self), key, None) if callable(cls_value): - setattr(self, key, ensure_async_callable(cls_value)) + setattr(self, key, cls_value) if not hasattr(self, "dto"): self.dto = Empty @@ -203,6 +204,41 @@ def __init__(self, owner: Router) -> None: self.path = normalize_path(self.path or "/") self.owner = owner + def as_router(self) -> Router: + from litestar.router import Router + + router = Router( + path=self.path, + route_handlers=self.get_route_handlers(), + after_request=self.after_request, + after_response=self.after_response, + before_request=self.before_request, + cache_control=self.cache_control, + dependencies=self.dependencies, + dto=self.dto, + etag=self.etag, + exception_handlers=self.exception_handlers, + guards=self.guards, + include_in_schema=self.include_in_schema, + middleware=self.middleware, + opt=self.opt, + parameters=self.parameters, + request_class=self.request_class, + response_class=self.response_class, + response_cookies=self.response_cookies, + response_headers=self.response_headers, + return_dto=self.return_dto, + security=self.security, + signature_types=self.signature_types, + signature_namespace=self.signature_namespace, + tags=self.tags, + type_encoders=self.type_encoders, + type_decoders=self.type_decoders, + websocket_class=self.websocket_class, + ) + router.owner = self.owner + return router + def get_route_handlers(self) -> list[BaseRouteHandler]: """Get a controller's route handlers and set the controller as the handlers' owner. @@ -222,7 +258,7 @@ def get_route_handlers(self) -> list[BaseRouteHandler]: route_handler = deepcopy(self_handler) # at the point we get a reference to the handler function, it's unbound, so # we replace it with a regular bound method here - route_handler._fn = types.MethodType(route_handler._fn, self) + route_handler.fn = types.MethodType(route_handler.fn, self) route_handler.owner = self route_handlers.append(route_handler) diff --git a/litestar/dto/_backend.py b/litestar/dto/_backend.py index 03a1768217..390a2084ca 100644 --- a/litestar/dto/_backend.py +++ b/litestar/dto/_backend.py @@ -701,6 +701,24 @@ def _transfer_type_data( ) return transfer_type.field_definition.instantiable_origin(source_value) + + if isinstance(transfer_type, MappingType): + if transfer_type.has_nested: + return transfer_type.field_definition.instantiable_origin( + ( + key, + _transfer_type_data( + source_value=value, + transfer_type=transfer_type.value_type, + nested_as_dict=False, + is_data_field=is_data_field, + ), + ) + for key, value in source_value.items() + ) + + return transfer_type.field_definition.instantiable_origin(source_value) + return source_value diff --git a/litestar/dto/_codegen_backend.py b/litestar/dto/_codegen_backend.py index deff908115..a3fb25f615 100644 --- a/litestar/dto/_codegen_backend.py +++ b/litestar/dto/_codegen_backend.py @@ -24,6 +24,7 @@ from litestar.dto._types import ( CollectionType, CompositeType, + MappingType, SimpleType, TransferDTOFieldDefinition, TransferType, @@ -506,6 +507,21 @@ def _create_transfer_type_data_body( self._add_stmt(f"{assignment_target} = {origin_name}({source_value_name})") return + if isinstance(transfer_type, MappingType): + origin_name = self._add_to_fn_globals("origin", transfer_type.field_definition.instantiable_origin) + if transfer_type.has_nested: + transfer_type_data_fn = TransferFunctionFactory.create_transfer_type_data( + is_data_field=self.is_data_field, transfer_type=transfer_type.value_type + ) + transfer_type_data_name = self._add_to_fn_globals("transfer_type_data", transfer_type_data_fn) + self._add_stmt( + f"{assignment_target} = {origin_name}((key, {transfer_type_data_name}(item)) for key, item in {source_value_name}.items())" + ) + return + + self._add_stmt(f"{assignment_target} = {origin_name}({source_value_name})") + return + self._add_stmt(f"{assignment_target} = {source_value_name}") def _create_transfer_nested_union_type_data( diff --git a/litestar/dto/base_dto.py b/litestar/dto/base_dto.py index 2976f7ce05..83fc16ce9c 100644 --- a/litestar/dto/base_dto.py +++ b/litestar/dto/base_dto.py @@ -217,10 +217,13 @@ def create_openapi_schema( # generated transfer model type in the type arguments. transfer_model = backend.transfer_model_type generic_args = tuple(transfer_model if a is cls.model_type else a for a in field_definition.args) - return schema_creator.for_field_definition( - FieldDefinition.from_annotation(field_definition.origin[generic_args]) - ) - return schema_creator.for_field_definition(FieldDefinition.from_annotation(backend.annotation)) + annotation = field_definition.safe_generic_origin[generic_args] + else: + annotation = backend.annotation + + return schema_creator.for_field_definition( + FieldDefinition.from_annotation(annotation, kwarg_definition=field_definition.kwarg_definition) + ) @classmethod def resolve_generic_wrapper_type( diff --git a/litestar/exceptions/responses/__init__.py b/litestar/exceptions/responses/__init__.py index e999e13a4f..9ed1df1c52 100644 --- a/litestar/exceptions/responses/__init__.py +++ b/litestar/exceptions/responses/__init__.py @@ -37,7 +37,6 @@ def to_response(self, request: Request | None = None) -> Response: Returns: A response instance. """ - from litestar.response import Response content: Any = {k: v for k, v in asdict(self).items() if k not in ("headers", "media_type") and v is not None} type_encoders = _debug_response._get_type_encoders_for_request(request) if request is not None else None diff --git a/litestar/handlers/asgi_handlers.py b/litestar/handlers/asgi_handlers.py index bcf220a0cc..03f0507308 100644 --- a/litestar/handlers/asgi_handlers.py +++ b/litestar/handlers/asgi_handlers.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Mapping, Sequence +from typing import TYPE_CHECKING, Any, Callable, Mapping, Sequence from litestar.exceptions import ImproperlyConfiguredException from litestar.handlers.base import BaseRouteHandler @@ -13,24 +13,20 @@ if TYPE_CHECKING: from litestar.connection import ASGIConnection from litestar.types import ( + AsyncAnyCallable, ExceptionHandlersMap, Guard, - MaybePartial, # noqa: F401 ) class ASGIRouteHandler(BaseRouteHandler): - """ASGI Route Handler decorator. - - Use this decorator to decorate ASGI applications. - """ - __slots__ = ("is_mount",) def __init__( self, path: str | Sequence[str] | None = None, *, + fn: AsyncAnyCallable, exception_handlers: ExceptionHandlersMap | None = None, guards: Sequence[Guard] | None = None, name: str | None = None, @@ -39,17 +35,20 @@ def __init__( signature_namespace: Mapping[str, Any] | None = None, **kwargs: Any, ) -> None: - """Initialize ``ASGIRouteHandler``. + """Route handler for ASGI routes. Args: + path: A path fragment for the route handler function or a list of path fragments. If not given defaults to + ``/``. + fn: The handler function. + + .. versionadded:: 3.0 exception_handlers: A mapping of status codes and/or exception types to handler functions. guards: A sequence of :class:`Guard <.types.Guard>` callables. name: A string identifying the route handler. opt: A string key mapping of arbitrary values that can be accessed in :class:`Guards <.types.Guard>` or wherever you have access to :class:`Request <.connection.Request>` or :class:`ASGI Scope <.types.Scope>`. - path: A path fragment for the route handler function or a list of path fragments. If not given defaults to - ``/`` is_mount: A boolean dictating whether the handler's paths should be regarded as mount paths. Mount path accept any arbitrary paths that begin with the defined prefixed path. For example, a mount with the path ``/some-path/`` will accept requests for ``/some-path/`` and any sub path under this, e.g. @@ -61,6 +60,7 @@ def __init__( self.is_mount = is_mount super().__init__( path, + fn=fn, exception_handlers=exception_handlers, guards=guards, name=name, @@ -101,4 +101,50 @@ async def handle(self, connection: ASGIConnection[ASGIRouteHandler, Any, Any, An await self.fn(scope=connection.scope, receive=connection.receive, send=connection.send) -asgi = ASGIRouteHandler +def asgi( + path: str | Sequence[str] | None = None, + *, + exception_handlers: ExceptionHandlersMap | None = None, + guards: Sequence[Guard] | None = None, + name: str | None = None, + opt: Mapping[str, Any] | None = None, + is_mount: bool = False, + signature_namespace: Mapping[str, Any] | None = None, + handler_class: type[ASGIRouteHandler] = ASGIRouteHandler, + **kwargs: Any, +) -> Callable[[AsyncAnyCallable], ASGIRouteHandler]: + """Create an :class:`ASGIRouteHandler`. + + Args: + path: A path fragment for the route handler function or a sequence of path fragments. If not given defaults + to ``/`` + exception_handlers: A mapping of status codes and/or exception types to handler functions. + guards: A sequence of :class:`Guard <.types.Guard>` callables. + name: A string identifying the route handler. + opt: A string keyed mapping of arbitrary values that can be accessed in :class:`Guards <.types.Guard>` or + wherever you have access to :class:`Request <.connection.Request>` or + :class:`ASGI Scope <.types.Scope>`. + signature_namespace: A mapping of names to types for use in forward reference resolution during signature + modelling. + is_mount: A boolean dictating whether the handler's paths should be regarded as mount paths. Mount path + accept any arbitrary paths that begin with the defined prefixed path. For example, a mount with the path + ``/some-path/`` will accept requests for ``/some-path/`` and any sub path under this, e.g. + ``/some-path/sub-path/`` etc. + handler_class: Route handler class instantiated by the decorator + **kwargs: Any additional kwarg - will be set in the opt dictionary. + """ + + def decorator(fn: AsyncAnyCallable) -> ASGIRouteHandler: + return handler_class( + fn=fn, + path=path, + exception_handlers=exception_handlers, + guards=guards, + name=name, + opt=opt, + is_mount=is_mount, + signature_namespace=signature_namespace, + **kwargs, + ) + + return decorator diff --git a/litestar/handlers/base.py b/litestar/handlers/base.py index c4932ecdb6..c3b74ab201 100644 --- a/litestar/handlers/base.py +++ b/litestar/handlers/base.py @@ -48,7 +48,6 @@ class BaseRouteHandler: """ __slots__ = ( - "_fn", "_parsed_data_field", "_parsed_fn_signature", "_parsed_return_field", @@ -64,6 +63,7 @@ class BaseRouteHandler: "dependencies", "dto", "exception_handlers", + "fn", "guards", "middleware", "name", @@ -80,6 +80,7 @@ def __init__( self, path: str | Sequence[str] | None = None, *, + fn: AsyncAnyCallable, dependencies: Dependencies | None = None, dto: type[AbstractDTO] | None | EmptyType = Empty, exception_handlers: ExceptionHandlersMap | None = None, @@ -99,6 +100,9 @@ def __init__( Args: path: A path fragment for the route handler function or a sequence of path fragments. If not given defaults to ``/`` + fn: The handler function + + .. versionadded:: 3.0 dependencies: A string keyed mapping of dependency :class:`Provider <.di.Provide>` instances. dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for (de)serializing and validation of request data. @@ -151,11 +155,10 @@ def __init__( self.paths = ( {normalize_path(p) for p in path} if path and isinstance(path, list) else {normalize_path(path or "/")} # type: ignore[arg-type] ) + self.fn = self._prepare_fn(fn) - def __call__(self, fn: AsyncAnyCallable) -> Self: - """Replace a function with itself.""" - self._fn = fn - return self + def _prepare_fn(self, fn: AsyncAnyCallable) -> AsyncAnyCallable: + return fn @property def handler_id(self) -> str: @@ -200,20 +203,6 @@ def signature_model(self) -> type[SignatureModel]: ) return self._signature_model - @property - def fn(self) -> AsyncAnyCallable: - """Get the handler function. - - Raises: - ImproperlyConfiguredException: if handler fn is not set. - - Returns: - Handler function - """ - if not hasattr(self, "_fn"): - raise ImproperlyConfiguredException("No callable has been registered for this handler") - return self._fn - @property def parsed_fn_signature(self) -> ParsedSignature: """Return the parsed signature of the handler function. @@ -434,13 +423,13 @@ def resolve_signature_namespace(self) -> dict[str, Any]: When merging keys from multiple layers, if the same key is defined by multiple layers, the value from the layer closest to the response handler will take precedence. """ - if self._resolved_layered_parameters is Empty: + if self._resolved_signature_namespace is Empty: ns: dict[str, Any] = {} for layer in self.ownership_layers: ns.update(layer.signature_namespace) self._resolved_signature_namespace = ns - return cast("dict[str, Any]", self._resolved_signature_namespace) + return self._resolved_signature_namespace def resolve_data_dto(self) -> type[AbstractDTO] | None: """Resolve the data_dto by starting from the route handler and moving up. diff --git a/litestar/handlers/http_handlers/__init__.py b/litestar/handlers/http_handlers/__init__.py index 844f046895..3009fda95b 100644 --- a/litestar/handlers/http_handlers/__init__.py +++ b/litestar/handlers/http_handlers/__init__.py @@ -1,7 +1,7 @@ from __future__ import annotations -from .base import HTTPRouteHandler, route -from .decorators import delete, get, head, patch, post, put +from .base import HTTPRouteHandler +from .decorators import delete, get, head, patch, post, put, route __all__ = ( "HTTPRouteHandler", diff --git a/litestar/handlers/http_handlers/_options.py b/litestar/handlers/http_handlers/_options.py index b46a3740b3..fb6a409114 100644 --- a/litestar/handlers/http_handlers/_options.py +++ b/litestar/handlers/http_handlers/_options.py @@ -33,8 +33,5 @@ def options_handler() -> Response: ) return HTTPRouteHandler( - path=path, - http_method=[HttpMethod.OPTIONS], - include_in_schema=False, - sync_to_thread=False, - )(options_handler) + path=path, http_method=[HttpMethod.OPTIONS], include_in_schema=False, sync_to_thread=False, fn=options_handler + ) diff --git a/litestar/handlers/http_handlers/base.py b/litestar/handlers/http_handlers/base.py index dcb11cd58a..1cc9227cfa 100644 --- a/litestar/handlers/http_handlers/base.py +++ b/litestar/handlers/http_handlers/base.py @@ -27,7 +27,8 @@ normalize_http_method, ) from litestar.openapi.spec import Operation -from litestar.response import Response +from litestar.response import File, Response +from litestar.response.file import ASGIFileResponse from litestar.status_codes import HTTP_204_NO_CONTENT, HTTP_304_NOT_MODIFIED from litestar.types import ( AfterRequestHookHandler, @@ -50,6 +51,7 @@ Send, TypeEncodersMap, ) +from litestar.types.builtin_types import NoneType from litestar.utils import ensure_async_callable from litestar.utils.predicates import is_async_callable from litestar.utils.scope.state import ScopeState @@ -71,7 +73,7 @@ from litestar.types.callable_types import AsyncAnyCallable, OperationIDCreator from litestar.types.composite_types import TypeDecodersSequence -__all__ = ("HTTPRouteHandler", "route") +__all__ = ("HTTPRouteHandler",) class ResponseHandlerMap(TypedDict): @@ -80,11 +82,6 @@ class ResponseHandlerMap(TypedDict): class HTTPRouteHandler(BaseRouteHandler): - """HTTP Route Decorator. - - Use this decorator to decorate an HTTP handler with multiple methods. - """ - __slots__ = ( "_resolved_after_response", "_resolved_before_request", @@ -128,12 +125,11 @@ class HTTPRouteHandler(BaseRouteHandler): "template_name", ) - has_sync_callable: bool - def __init__( self, path: str | Sequence[str] | None = None, *, + fn: AnyCallable, after_request: AfterRequestHookHandler | None = None, after_response: AfterResponseHookHandler | None = None, background: BackgroundTask | BackgroundTasks | None = None, @@ -177,11 +173,12 @@ def __init__( type_encoders: TypeEncodersMap | None = None, **kwargs: Any, ) -> None: - """Initialize ``HTTPRouteHandler``. + """Route handler for HTTP routes. Args: path: A path fragment for the route handler function or a sequence of path fragments. If not given defaults to ``/`` + fn: The handler function after_request: A sync or async function executed before a :class:`Request <.connection.Request>` is passed to any route handler. If this function returns a value, the request will not reach the route handler, and instead this value will be used. @@ -255,7 +252,18 @@ def __init__( self.http_methods = normalize_http_method(http_methods=http_method) self.status_code = status_code or get_default_status_code(http_methods=self.http_methods) + if has_sync_callable := not is_async_callable(fn): + if sync_to_thread is None: + warn_implicit_sync_to_thread(fn, stacklevel=3) + elif sync_to_thread is not None: + warn_sync_to_thread_with_async_callable(fn, stacklevel=3) + + if has_sync_callable and sync_to_thread: + fn = ensure_async_callable(fn) + has_sync_callable = False + super().__init__( + fn=fn, path=path, dependencies=dependencies, dto=dto, @@ -285,7 +293,7 @@ def __init__( self.response_cookies: Sequence[Cookie] | None = narrow_response_cookies(response_cookies) self.response_headers: Sequence[ResponseHeader] | None = narrow_response_headers(response_headers) - self.sync_to_thread = sync_to_thread + self.has_sync_callable = has_sync_callable # OpenAPI related attributes self.content_encoding = content_encoding self.content_media_type = content_media_type @@ -311,17 +319,6 @@ def __init__( self._resolved_tags: list[str] | EmptyType = Empty self._kwargs_models: dict[tuple[str, ...], KwargsModel] = {} - def __call__(self, fn: AnyCallable) -> HTTPRouteHandler: - """Replace a function with itself.""" - if not is_async_callable(fn): - if self.sync_to_thread is None: - warn_implicit_sync_to_thread(fn, stacklevel=3) - elif self.sync_to_thread is not None: - warn_sync_to_thread_with_async_callable(fn, stacklevel=3) - - super().__call__(fn) - return self - def resolve_request_class(self) -> type[Request]: """Return the closest custom Request class in the owner graph or the default Request class. @@ -573,11 +570,6 @@ def on_registration(self, app: Litestar, route: BaseRoute) -> None: super().on_registration(app, route=route) self.resolve_after_response() self.resolve_include_in_schema() - self.has_sync_callable = not is_async_callable(self.fn) - - if self.has_sync_callable and self.sync_to_thread: - self._fn = ensure_async_callable(self.fn) - self.has_sync_callable = False self._get_kwargs_model_for_route(route.path_parameters) @@ -619,6 +611,11 @@ def _validate_handler_function(self) -> None: if "data" in self.parsed_fn_signature.parameters and "GET" in self.http_methods: raise ImproperlyConfiguredException("'data' kwarg is unsupported for 'GET' request handlers") + if self.http_methods == {HttpMethod.HEAD} and not self.parsed_fn_signature.return_type.is_subclass_of( + (NoneType, File, ASGIFileResponse) + ): + raise ImproperlyConfiguredException("A response to a head request should not have a body") + async def handle(self, connection: Request[Any, Any, Any]) -> None: """ASGI app that creates a :class:`~.connection.Request` from the passed in args, determines which handler function to call and then handles the call. @@ -754,6 +751,3 @@ async def cached_response(scope: Scope, receive: Receive, send: Send) -> None: await send(message) return cached_response - - -route = HTTPRouteHandler diff --git a/litestar/handlers/http_handlers/decorators.py b/litestar/handlers/http_handlers/decorators.py index 1ae72e559b..02e304713a 100644 --- a/litestar/handlers/http_handlers/decorators.py +++ b/litestar/handlers/http_handlers/decorators.py @@ -1,16 +1,29 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any, Callable, Mapping, Sequence from litestar.enums import HttpMethod, MediaType -from litestar.exceptions import HTTPException, ImproperlyConfiguredException -from litestar.openapi.spec import Operation -from litestar.response.file import ASGIFileResponse, File -from litestar.types import Empty, TypeDecodersSequence -from litestar.types.builtin_types import NoneType -from litestar.utils import is_class_and_subclass - -from .base import HTTPRouteHandler +from litestar.handlers.http_handlers.base import HTTPRouteHandler +from litestar.openapi.spec import Operation, SecurityRequirement +from litestar.types import ( + AfterRequestHookHandler, + AfterResponseHookHandler, + AnyCallable, + BeforeRequestHookHandler, + CacheKeyBuilder, + Dependencies, + Empty, + EmptyType, + ExceptionHandlersMap, + Guard, + Method, + Middleware, + OperationIDCreator, + ResponseCookies, + ResponseHeaders, + TypeDecodersSequence, + TypeEncodersMap, +) if TYPE_CHECKING: from typing import Any, Mapping, Sequence @@ -20,6 +33,7 @@ from litestar.connection import Request from litestar.datastructures import CacheControlHeader, ETag from litestar.dto import AbstractDTO + from litestar.exceptions import HTTPException from litestar.openapi.datastructures import ResponseSpec from litestar.openapi.spec import SecurityRequirement from litestar.response import Response @@ -37,141 +51,137 @@ ResponseHeaders, TypeEncodersMap, ) - from litestar.types.callable_types import OperationIDCreator - + from litestar.types.callable_types import AnyCallable, OperationIDCreator -__all__ = ("get", "head", "post", "put", "patch", "delete") +__all__ = ("get", "head", "post", "put", "patch", "delete", "route") -MSG_SEMANTIC_ROUTE_HANDLER_WITH_HTTP = "semantic route handlers cannot define http_method" +def route( + path: str | None | Sequence[str] = None, + *, + http_method: HttpMethod | Method | Sequence[HttpMethod | Method], + after_request: AfterRequestHookHandler | None = None, + after_response: AfterResponseHookHandler | None = None, + background: BackgroundTask | BackgroundTasks | None = None, + before_request: BeforeRequestHookHandler | None = None, + cache: bool | int | type[CACHE_FOREVER] = False, + cache_control: CacheControlHeader | None = None, + cache_key_builder: CacheKeyBuilder | None = None, + dependencies: Dependencies | None = None, + dto: type[AbstractDTO] | None | EmptyType = Empty, + etag: ETag | None = None, + exception_handlers: ExceptionHandlersMap | None = None, + guards: Sequence[Guard] | None = None, + media_type: MediaType | str | None = None, + middleware: Sequence[Middleware] | None = None, + name: str | None = None, + opt: Mapping[str, Any] | None = None, + request_class: type[Request] | None = None, + response_class: type[Response] | None = None, + response_cookies: ResponseCookies | None = None, + response_headers: ResponseHeaders | None = None, + return_dto: type[AbstractDTO] | None | EmptyType = Empty, + signature_namespace: Mapping[str, Any] | None = None, + status_code: int | None = None, + sync_to_thread: bool | None = None, + # OpenAPI related attributes + content_encoding: str | None = None, + content_media_type: str | None = None, + deprecated: bool = False, + description: str | None = None, + include_in_schema: bool | EmptyType = Empty, + operation_class: type[Operation] = Operation, + operation_id: str | OperationIDCreator | None = None, + raises: Sequence[type[HTTPException]] | None = None, + response_description: str | None = None, + responses: Mapping[int, ResponseSpec] | None = None, + security: Sequence[SecurityRequirement] | None = None, + summary: str | None = None, + tags: Sequence[str] | None = None, + type_decoders: TypeDecodersSequence | None = None, + type_encoders: TypeEncodersMap | None = None, + handler_class: type[HTTPRouteHandler] = HTTPRouteHandler, + **kwargs: Any, +) -> Callable[[AnyCallable], HTTPRouteHandler]: + """Create an :class:`HTTPRouteHandler`. -class delete(HTTPRouteHandler): - """DELETE Route Decorator. + Args: + path: A path fragment for the route handler function or a sequence of path fragments. + If not given defaults to ``/`` + after_request: A sync or async function executed before a :class:`Request <.connection.Request>` is passed + to any route handler. If this function returns a value, the request will not reach the route handler, + and instead this value will be used. + after_response: A sync or async function called after the response has been awaited. It receives the + :class:`Request <.connection.Request>` object and should not return any values. + background: A :class:`BackgroundTask <.background_tasks.BackgroundTask>` instance or + :class:`BackgroundTasks <.background_tasks.BackgroundTasks>` to execute after the response is finished. + Defaults to ``None``. + before_request: A sync or async function called immediately before calling the route handler. Receives + the :class:`.connection.Request` instance and any non-``None`` return value is used for the response, + bypassing the route handler. + cache: Enables response caching if configured on the application level. Valid values are ``True`` or a number + of seconds (e.g. ``120``) to cache the response. + cache_control: A ``cache-control`` header of type + :class:`CacheControlHeader <.datastructures.CacheControlHeader>` that will be added to the response. + cache_key_builder: A :class:`cache-key builder function <.types.CacheKeyBuilder>`. Allows for customization + of the cache key if caching is configured on the application level. + dependencies: A string keyed mapping of dependency :class:`Provider <.di.Provide>` instances. + dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for (de)serializing and + validation of request data. + etag: An ``etag`` header of type :class:`ETag <.datastructures.ETag>` that will be added to the response. + exception_handlers: A mapping of status codes and/or exception types to handler functions. + guards: A sequence of :class:`Guard <.types.Guard>` callables. + http_method: An :class:`http method string <.types.Method>`, a member of the enum + :class:`HttpMethod ` or a list of these that correlates to the methods the + route handler function should handle. + media_type: A member of the :class:`MediaType <.enums.MediaType>` enum or a string with a + valid IANA Media-Type. + middleware: A sequence of :class:`Middleware <.types.Middleware>`. + name: A string identifying the route handler. + opt: A string keyed mapping of arbitrary values that can be accessed in :class:`Guards <.types.Guard>` or + wherever you have access to :class:`Request <.connection.Request>` or :class:`ASGI Scope <.types.Scope>`. + request_class: A custom subclass of :class:`Request <.connection.Request>` to be used as route handler's + default request. + response_class: A custom subclass of :class:`Response <.response.Response>` to be used as route handler's + default response. + response_cookies: A sequence of :class:`Cookie <.datastructures.Cookie>` instances. + response_headers: A string keyed mapping of :class:`ResponseHeader <.datastructures.ResponseHeader>` + instances. + responses: A mapping of additional status codes and a description of their expected content. + This information will be included in the OpenAPI schema + return_dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for serializing + outbound response data. + signature_namespace: A mapping of names to types for use in forward reference resolution during signature modelling. + status_code: An http status code for the response. Defaults to ``200`` for mixed method or ``GET``, ``PUT`` and + ``PATCH``, ``201`` for ``POST`` and ``204`` for ``DELETE``. + sync_to_thread: A boolean dictating whether the handler function will be executed in a worker thread or the + main event loop. This has an effect only for sync handler functions. See using sync handler functions. + content_encoding: A string describing the encoding of the content, e.g. ``base64``. + content_media_type: A string designating the media-type of the content, e.g. ``image/png``. + deprecated: A boolean dictating whether this route should be marked as deprecated in the OpenAPI schema. + description: Text used for the route's schema description section. + include_in_schema: A boolean flag dictating whether the route handler should be documented in the OpenAPI schema. + operation_class: :class:`Operation <.openapi.spec.operation.Operation>` to be used with the route's OpenAPI schema. + operation_id: Either a string or a callable returning a string. An identifier used for the route's schema operationId. + raises: A list of exception classes extending from litestar.HttpException that is used for the OpenAPI documentation. + This list should describe all exceptions raised within the route handler's function/method. The Litestar + ValidationException will be added automatically for the schema if any validation is involved. + response_description: Text used for the route's response schema description section. + security: A sequence of dictionaries that contain information about which security scheme can be used on the endpoint. + summary: Text used for the route's schema summary section. + tags: A sequence of string tags that will be appended to the OpenAPI schema. + type_decoders: A sequence of tuples, each composed of a predicate testing for type identity and a msgspec + hook for deserialization. + type_encoders: A mapping of types to callables that transform them into types supported for serialization. + handler_class: Route handler class instantiated by the decorator - Use this decorator to decorate an HTTP handler for DELETE requests. + **kwargs: Any additional kwarg - will be set in the opt dictionary. """ - def __init__( - self, - path: str | None | Sequence[str] = None, - *, - after_request: AfterRequestHookHandler | None = None, - after_response: AfterResponseHookHandler | None = None, - background: BackgroundTask | BackgroundTasks | None = None, - before_request: BeforeRequestHookHandler | None = None, - cache: bool | int | type[CACHE_FOREVER] = False, - cache_control: CacheControlHeader | None = None, - cache_key_builder: CacheKeyBuilder | None = None, - dependencies: Dependencies | None = None, - dto: type[AbstractDTO] | None | EmptyType = Empty, - etag: ETag | None = None, - exception_handlers: ExceptionHandlersMap | None = None, - guards: Sequence[Guard] | None = None, - media_type: MediaType | str | None = None, - middleware: Sequence[Middleware] | None = None, - name: str | None = None, - opt: Mapping[str, Any] | None = None, - request_class: type[Request] | None = None, - response_class: type[Response] | None = None, - response_cookies: ResponseCookies | None = None, - response_headers: ResponseHeaders | None = None, - return_dto: type[AbstractDTO] | None | EmptyType = Empty, - signature_namespace: Mapping[str, Any] | None = None, - status_code: int | None = None, - sync_to_thread: bool | None = None, - # OpenAPI related attributes - content_encoding: str | None = None, - content_media_type: str | None = None, - deprecated: bool = False, - description: str | None = None, - include_in_schema: bool | EmptyType = Empty, - operation_class: type[Operation] = Operation, - operation_id: str | OperationIDCreator | None = None, - raises: Sequence[type[HTTPException]] | None = None, - response_description: str | None = None, - responses: Mapping[int, ResponseSpec] | None = None, - security: Sequence[SecurityRequirement] | None = None, - summary: str | None = None, - tags: Sequence[str] | None = None, - type_decoders: TypeDecodersSequence | None = None, - type_encoders: TypeEncodersMap | None = None, - **kwargs: Any, - ) -> None: - """Initialize ``delete`` - - Args: - path: A path fragment for the route handler function or a sequence of path fragments. - If not given defaults to ``/`` - after_request: A sync or async function executed before a :class:`Request <.connection.Request>` is passed - to any route handler. If this function returns a value, the request will not reach the route handler, - and instead this value will be used. - after_response: A sync or async function called after the response has been awaited. It receives the - :class:`Request <.connection.Request>` object and should not return any values. - background: A :class:`BackgroundTask <.background_tasks.BackgroundTask>` instance or - :class:`BackgroundTasks <.background_tasks.BackgroundTasks>` to execute after the response is finished. - Defaults to ``None``. - before_request: A sync or async function called immediately before calling the route handler. Receives - the :class:`.connection.Request` instance and any non-``None`` return value is used for the response, - bypassing the route handler. - cache: Enables response caching if configured on the application level. Valid values are ``True`` or a number - of seconds (e.g. ``120``) to cache the response. - cache_control: A ``cache-control`` header of type - :class:`CacheControlHeader <.datastructures.CacheControlHeader>` that will be added to the response. - cache_key_builder: A :class:`cache-key builder function <.types.CacheKeyBuilder>`. Allows for customization - of the cache key if caching is configured on the application level. - dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for (de)serializing and - validation of request data. - dependencies: A string keyed mapping of dependency :class:`Provider <.di.Provide>` instances. - etag: An ``etag`` header of type :class:`ETag <.datastructures.ETag>` that will be added to the response. - exception_handlers: A mapping of status codes and/or exception types to handler functions. - guards: A sequence of :class:`Guard <.types.Guard>` callables. - http_method: An :class:`http method string <.types.Method>`, a member of the enum - :class:`HttpMethod ` or a list of these that correlates to the methods the - route handler function should handle. - media_type: A member of the :class:`MediaType <.enums.MediaType>` enum or a string with a - valid IANA Media-Type. - middleware: A sequence of :class:`Middleware <.types.Middleware>`. - name: A string identifying the route handler. - opt: A string keyed mapping of arbitrary values that can be accessed in :class:`Guards <.types.Guard>` or - wherever you have access to :class:`Request <.connection.Request>` or :class:`ASGI Scope <.types.Scope>`. - request_class: A custom subclass of :class:`Request <.connection.Request>` to be used as route handler's - default request. - response_class: A custom subclass of :class:`Response <.response.Response>` to be used as route handler's - default response. - response_cookies: A sequence of :class:`Cookie <.datastructures.Cookie>` instances. - response_headers: A string keyed mapping of :class:`ResponseHeader <.datastructures.ResponseHeader>` - instances. - responses: A mapping of additional status codes and a description of their expected content. - This information will be included in the OpenAPI schema - return_dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for serializing - outbound response data. - signature_namespace: A mapping of names to types for use in forward reference resolution during signature modelling. - status_code: An http status code for the response. Defaults to ``200`` for mixed method or ``GET``, ``PUT`` - and ``PATCH``, ``201`` for ``POST`` and ``204`` for ``DELETE``. - sync_to_thread: A boolean dictating whether the handler function will be executed in a worker thread or the - main event loop. This has an effect only for sync handler functions. See using sync handler functions. - content_encoding: A string describing the encoding of the content, e.g. ``base64``. - content_media_type: A string designating the media-type of the content, e.g. ``image/png``. - deprecated: A boolean dictating whether this route should be marked as deprecated in the OpenAPI schema. - description: Text used for the route's schema description section. - include_in_schema: A boolean flag dictating whether the route handler should be documented in the OpenAPI schema. - operation_class: :class:`Operation <.openapi.spec.operation.Operation>` to be used with the route's OpenAPI schema. - operation_id: Either a string or a callable returning a string. An identifier used for the route's schema operationId. - raises: A list of exception classes extending from litestar.HttpException that is used for the OpenAPI documentation. - This list should describe all exceptions raised within the route handler's function/method. The Litestar - ValidationException will be added automatically for the schema if any validation is involved. - response_description: Text used for the route's response schema description section. - security: A sequence of dictionaries that contain information about which security scheme can be used on the endpoint. - summary: Text used for the route's schema summary section. - tags: A sequence of string tags that will be appended to the OpenAPI schema. - type_decoders: A sequence of tuples, each composed of a predicate testing for type identity and a msgspec - hook for deserialization. - type_encoders: A mapping of types to callables that transform them into types supported for serialization. - **kwargs: Any additional kwarg - will be set in the opt dictionary. - """ - if "http_method" in kwargs: - raise ImproperlyConfiguredException(MSG_SEMANTIC_ROUTE_HANDLER_WITH_HTTP) - super().__init__( + def decorator(fn: AnyCallable) -> HTTPRouteHandler: + return handler_class( + fn=fn, + http_method=http_method, after_request=after_request, after_response=after_response, background=background, @@ -188,7 +198,6 @@ def __init__( etag=etag, exception_handlers=exception_handlers, guards=guards, - http_method=HttpMethod.DELETE, include_in_schema=include_in_schema, media_type=media_type, middleware=middleware, @@ -216,135 +225,130 @@ def __init__( **kwargs, ) + return decorator -class get(HTTPRouteHandler): - """GET Route Decorator. - Use this decorator to decorate an HTTP handler for GET requests. - """ +def get( + path: str | None | Sequence[str] = None, + *, + after_request: AfterRequestHookHandler | None = None, + after_response: AfterResponseHookHandler | None = None, + background: BackgroundTask | BackgroundTasks | None = None, + before_request: BeforeRequestHookHandler | None = None, + cache: bool | int | type[CACHE_FOREVER] = False, + cache_control: CacheControlHeader | None = None, + cache_key_builder: CacheKeyBuilder | None = None, + dependencies: Dependencies | None = None, + dto: type[AbstractDTO] | None | EmptyType = Empty, + etag: ETag | None = None, + exception_handlers: ExceptionHandlersMap | None = None, + guards: Sequence[Guard] | None = None, + media_type: MediaType | str | None = None, + middleware: Sequence[Middleware] | None = None, + name: str | None = None, + opt: Mapping[str, Any] | None = None, + request_class: type[Request] | None = None, + response_class: type[Response] | None = None, + response_cookies: ResponseCookies | None = None, + response_headers: ResponseHeaders | None = None, + return_dto: type[AbstractDTO] | None | EmptyType = Empty, + signature_namespace: Mapping[str, Any] | None = None, + status_code: int | None = None, + sync_to_thread: bool | None = None, + # OpenAPI related attributes + content_encoding: str | None = None, + content_media_type: str | None = None, + deprecated: bool = False, + description: str | None = None, + include_in_schema: bool | EmptyType = Empty, + operation_class: type[Operation] = Operation, + operation_id: str | OperationIDCreator | None = None, + raises: Sequence[type[HTTPException]] | None = None, + response_description: str | None = None, + responses: Mapping[int, ResponseSpec] | None = None, + security: Sequence[SecurityRequirement] | None = None, + summary: str | None = None, + tags: Sequence[str] | None = None, + type_decoders: TypeDecodersSequence | None = None, + type_encoders: TypeEncodersMap | None = None, + handler_class: type[HTTPRouteHandler] = HTTPRouteHandler, + **kwargs: Any, +) -> Callable[[AnyCallable], HTTPRouteHandler]: + """Create an :class:`HTTPRouteHandler` with a ``GET`` method. - def __init__( - self, - path: str | None | Sequence[str] = None, - *, - after_request: AfterRequestHookHandler | None = None, - after_response: AfterResponseHookHandler | None = None, - background: BackgroundTask | BackgroundTasks | None = None, - before_request: BeforeRequestHookHandler | None = None, - cache: bool | int | type[CACHE_FOREVER] = False, - cache_control: CacheControlHeader | None = None, - cache_key_builder: CacheKeyBuilder | None = None, - dependencies: Dependencies | None = None, - dto: type[AbstractDTO] | None | EmptyType = Empty, - etag: ETag | None = None, - exception_handlers: ExceptionHandlersMap | None = None, - guards: Sequence[Guard] | None = None, - media_type: MediaType | str | None = None, - middleware: Sequence[Middleware] | None = None, - name: str | None = None, - opt: Mapping[str, Any] | None = None, - request_class: type[Request] | None = None, - response_class: type[Response] | None = None, - response_cookies: ResponseCookies | None = None, - response_headers: ResponseHeaders | None = None, - return_dto: type[AbstractDTO] | None | EmptyType = Empty, - signature_namespace: Mapping[str, Any] | None = None, - status_code: int | None = None, - sync_to_thread: bool | None = None, - # OpenAPI related attributes - content_encoding: str | None = None, - content_media_type: str | None = None, - deprecated: bool = False, - description: str | None = None, - include_in_schema: bool | EmptyType = Empty, - operation_class: type[Operation] = Operation, - operation_id: str | OperationIDCreator | None = None, - raises: Sequence[type[HTTPException]] | None = None, - response_description: str | None = None, - responses: Mapping[int, ResponseSpec] | None = None, - security: Sequence[SecurityRequirement] | None = None, - summary: str | None = None, - tags: Sequence[str] | None = None, - type_decoders: TypeDecodersSequence | None = None, - type_encoders: TypeEncodersMap | None = None, - **kwargs: Any, - ) -> None: - """Initialize ``get``. + Args: + path: A path fragment for the route handler function or a sequence of path fragments. + If not given defaults to ``/`` + after_request: A sync or async function executed before a :class:`Request <.connection.Request>` is passed + to any route handler. If this function returns a value, the request will not reach the route handler, + and instead this value will be used. + after_response: A sync or async function called after the response has been awaited. It receives the + :class:`Request <.connection.Request>` object and should not return any values. + background: A :class:`BackgroundTask <.background_tasks.BackgroundTask>` instance or + :class:`BackgroundTasks <.background_tasks.BackgroundTasks>` to execute after the response is finished. + Defaults to ``None``. + before_request: A sync or async function called immediately before calling the route handler. Receives + the :class:`.connection.Request` instance and any non-``None`` return value is used for the response, + bypassing the route handler. + cache: Enables response caching if configured on the application level. Valid values are ``True`` or a number + of seconds (e.g. ``120``) to cache the response. + cache_control: A ``cache-control`` header of type + :class:`CacheControlHeader <.datastructures.CacheControlHeader>` that will be added to the response. + cache_key_builder: A :class:`cache-key builder function <.types.CacheKeyBuilder>`. Allows for customization + of the cache key if caching is configured on the application level. + dependencies: A string keyed mapping of dependency :class:`Provider <.di.Provide>` instances. + dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for (de)serializing and + validation of request data. + etag: An ``etag`` header of type :class:`ETag <.datastructures.ETag>` that will be added to the response. + exception_handlers: A mapping of status codes and/or exception types to handler functions. + guards: A sequence of :class:`Guard <.types.Guard>` callables. + media_type: A member of the :class:`MediaType <.enums.MediaType>` enum or a string with a + valid IANA Media-Type. + middleware: A sequence of :class:`Middleware <.types.Middleware>`. + name: A string identifying the route handler. + opt: A string keyed mapping of arbitrary values that can be accessed in :class:`Guards <.types.Guard>` or + wherever you have access to :class:`Request <.connection.Request>` or :class:`ASGI Scope <.types.Scope>`. + request_class: A custom subclass of :class:`Request <.connection.Request>` to be used as route handler's + default request. + response_class: A custom subclass of :class:`Response <.response.Response>` to be used as route handler's + default response. + response_cookies: A sequence of :class:`Cookie <.datastructures.Cookie>` instances. + response_headers: A string keyed mapping of :class:`ResponseHeader <.datastructures.ResponseHeader>` + instances. + responses: A mapping of additional status codes and a description of their expected content. + This information will be included in the OpenAPI schema + return_dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for serializing + outbound response data. + signature_namespace: A mapping of names to types for use in forward reference resolution during signature modelling. + status_code: An http status code for the response. Defaults to ``200`` for mixed method or ``GET``, ``PUT`` and + ``PATCH``, ``201`` for ``POST`` and ``204`` for ``DELETE``. + sync_to_thread: A boolean dictating whether the handler function will be executed in a worker thread or the + main event loop. This has an effect only for sync handler functions. See using sync handler functions. + content_encoding: A string describing the encoding of the content, e.g. ``base64``. + content_media_type: A string designating the media-type of the content, e.g. ``image/png``. + deprecated: A boolean dictating whether this route should be marked as deprecated in the OpenAPI schema. + description: Text used for the route's schema description section. + include_in_schema: A boolean flag dictating whether the route handler should be documented in the OpenAPI schema. + operation_class: :class:`Operation <.openapi.spec.operation.Operation>` to be used with the route's OpenAPI schema. + operation_id: Either a string or a callable returning a string. An identifier used for the route's schema operationId. + raises: A list of exception classes extending from litestar.HttpException that is used for the OpenAPI documentation. + This list should describe all exceptions raised within the route handler's function/method. The Litestar + ValidationException will be added automatically for the schema if any validation is involved. + response_description: Text used for the route's response schema description section. + security: A sequence of dictionaries that contain information about which security scheme can be used on the endpoint. + summary: Text used for the route's schema summary section. + tags: A sequence of string tags that will be appended to the OpenAPI schema. + type_decoders: A sequence of tuples, each composed of a predicate testing for type identity and a msgspec + hook for deserialization. + type_encoders: A mapping of types to callables that transform them into types supported for serialization. + handler_class: Route handler class instantiated by the decorator - Args: - path: A path fragment for the route handler function or a sequence of path fragments. - If not given defaults to ``/`` - after_request: A sync or async function executed before a :class:`Request <.connection.Request>` is passed - to any route handler. If this function returns a value, the request will not reach the route handler, - and instead this value will be used. - after_response: A sync or async function called after the response has been awaited. It receives the - :class:`Request <.connection.Request>` object and should not return any values. - background: A :class:`BackgroundTask <.background_tasks.BackgroundTask>` instance or - :class:`BackgroundTasks <.background_tasks.BackgroundTasks>` to execute after the response is finished. - Defaults to ``None``. - before_request: A sync or async function called immediately before calling the route handler. Receives - the :class:`.connection.Request` instance and any non-``None`` return value is used for the response, - bypassing the route handler. - cache: Enables response caching if configured on the application level. Valid values are ``True`` or a number - of seconds (e.g. ``120``) to cache the response. - cache_control: A ``cache-control`` header of type - :class:`CacheControlHeader <.datastructures.CacheControlHeader>` that will be added to the response. - cache_key_builder: A :class:`cache-key builder function <.types.CacheKeyBuilder>`. Allows for customization - of the cache key if caching is configured on the application level. - dependencies: A string keyed mapping of dependency :class:`Provider <.di.Provide>` instances. - dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for (de)serializing and - validation of request data. - etag: An ``etag`` header of type :class:`ETag <.datastructures.ETag>` that will be added to the response. - exception_handlers: A mapping of status codes and/or exception types to handler functions. - guards: A sequence of :class:`Guard <.types.Guard>` callables. - http_method: An :class:`http method string <.types.Method>`, a member of the enum - :class:`HttpMethod ` or a list of these that correlates to the methods the - route handler function should handle. - media_type: A member of the :class:`MediaType <.enums.MediaType>` enum or a string with a - valid IANA Media-Type. - middleware: A sequence of :class:`Middleware <.types.Middleware>`. - name: A string identifying the route handler. - opt: A string keyed mapping of arbitrary values that can be accessed in :class:`Guards <.types.Guard>` or - wherever you have access to :class:`Request <.connection.Request>` or :class:`ASGI Scope <.types.Scope>`. - request_class: A custom subclass of :class:`Request <.connection.Request>` to be used as route handler's - default request. - response_class: A custom subclass of :class:`Response <.response.Response>` to be used as route handler's - default response. - response_cookies: A sequence of :class:`Cookie <.datastructures.Cookie>` instances. - response_headers: A string keyed mapping of :class:`ResponseHeader <.datastructures.ResponseHeader>` - instances. - responses: A mapping of additional status codes and a description of their expected content. - This information will be included in the OpenAPI schema - return_dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for serializing - outbound response data. - signature_namespace: A mapping of names to types for use in forward reference resolution during signature modelling. - status_code: An http status code for the response. Defaults to ``200`` for mixed method or ``GET``, ``PUT`` and - ``PATCH``, ``201`` for ``POST`` and ``204`` for ``DELETE``. - sync_to_thread: A boolean dictating whether the handler function will be executed in a worker thread or the - main event loop. This has an effect only for sync handler functions. See using sync handler functions. - content_encoding: A string describing the encoding of the content, e.g. ``base64``. - content_media_type: A string designating the media-type of the content, e.g. ``image/png``. - deprecated: A boolean dictating whether this route should be marked as deprecated in the OpenAPI schema. - description: Text used for the route's schema description section. - include_in_schema: A boolean flag dictating whether the route handler should be documented in the OpenAPI schema. - operation_class: :class:`Operation <.openapi.spec.operation.Operation>` to be used with the route's OpenAPI schema. - operation_id: Either a string or a callable returning a string. An identifier used for the route's schema operationId. - raises: A list of exception classes extending from litestar.HttpException that is used for the OpenAPI documentation. - This list should describe all exceptions raised within the route handler's function/method. The Litestar - ValidationException will be added automatically for the schema if any validation is involved. - response_description: Text used for the route's response schema description section. - security: A sequence of dictionaries that contain information about which security scheme can be used on the endpoint. - summary: Text used for the route's schema summary section. - tags: A sequence of string tags that will be appended to the OpenAPI schema. - type_decoders: A sequence of tuples, each composed of a predicate testing for type identity and a msgspec - hook for deserialization. - type_encoders: A mapping of types to callables that transform them into types supported for serialization. - **kwargs: Any additional kwarg - will be set in the opt dictionary. - """ - if "http_method" in kwargs: - raise ImproperlyConfiguredException(MSG_SEMANTIC_ROUTE_HANDLER_WITH_HTTP) + **kwargs: Any additional kwarg - will be set in the opt dictionary. + """ - super().__init__( + def decorator(fn: AnyCallable) -> HTTPRouteHandler: + return handler_class( + fn=fn, after_request=after_request, after_response=after_response, background=background, @@ -389,139 +393,134 @@ def __init__( **kwargs, ) + return decorator -class head(HTTPRouteHandler): - """HEAD Route Decorator. - Use this decorator to decorate an HTTP handler for HEAD requests. - """ +def head( + path: str | None | Sequence[str] = None, + *, + after_request: AfterRequestHookHandler | None = None, + after_response: AfterResponseHookHandler | None = None, + background: BackgroundTask | BackgroundTasks | None = None, + before_request: BeforeRequestHookHandler | None = None, + cache: bool | int | type[CACHE_FOREVER] = False, + cache_control: CacheControlHeader | None = None, + cache_key_builder: CacheKeyBuilder | None = None, + dependencies: Dependencies | None = None, + dto: type[AbstractDTO] | None | EmptyType = Empty, + etag: ETag | None = None, + exception_handlers: ExceptionHandlersMap | None = None, + guards: Sequence[Guard] | None = None, + media_type: MediaType | str | None = None, + middleware: Sequence[Middleware] | None = None, + name: str | None = None, + opt: Mapping[str, Any] | None = None, + request_class: type[Request] | None = None, + response_class: type[Response] | None = None, + response_cookies: ResponseCookies | None = None, + response_headers: ResponseHeaders | None = None, + signature_namespace: Mapping[str, Any] | None = None, + status_code: int | None = None, + sync_to_thread: bool | None = None, + # OpenAPI related attributes + content_encoding: str | None = None, + content_media_type: str | None = None, + deprecated: bool = False, + description: str | None = None, + include_in_schema: bool | EmptyType = Empty, + operation_class: type[Operation] = Operation, + operation_id: str | OperationIDCreator | None = None, + raises: Sequence[type[HTTPException]] | None = None, + response_description: str | None = None, + responses: Mapping[int, ResponseSpec] | None = None, + return_dto: type[AbstractDTO] | None | EmptyType = Empty, + security: Sequence[SecurityRequirement] | None = None, + summary: str | None = None, + tags: Sequence[str] | None = None, + type_decoders: TypeDecodersSequence | None = None, + type_encoders: TypeEncodersMap | None = None, + handler_class: type[HTTPRouteHandler] = HTTPRouteHandler, + **kwargs: Any, +) -> Callable[[AnyCallable], HTTPRouteHandler]: + """Create an :class:`HTTPRouteHandler` with a ``HEAD`` method. - def __init__( - self, - path: str | None | Sequence[str] = None, - *, - after_request: AfterRequestHookHandler | None = None, - after_response: AfterResponseHookHandler | None = None, - background: BackgroundTask | BackgroundTasks | None = None, - before_request: BeforeRequestHookHandler | None = None, - cache: bool | int | type[CACHE_FOREVER] = False, - cache_control: CacheControlHeader | None = None, - cache_key_builder: CacheKeyBuilder | None = None, - dependencies: Dependencies | None = None, - dto: type[AbstractDTO] | None | EmptyType = Empty, - etag: ETag | None = None, - exception_handlers: ExceptionHandlersMap | None = None, - guards: Sequence[Guard] | None = None, - media_type: MediaType | str | None = None, - middleware: Sequence[Middleware] | None = None, - name: str | None = None, - opt: Mapping[str, Any] | None = None, - request_class: type[Request] | None = None, - response_class: type[Response] | None = None, - response_cookies: ResponseCookies | None = None, - response_headers: ResponseHeaders | None = None, - signature_namespace: Mapping[str, Any] | None = None, - status_code: int | None = None, - sync_to_thread: bool | None = None, - # OpenAPI related attributes - content_encoding: str | None = None, - content_media_type: str | None = None, - deprecated: bool = False, - description: str | None = None, - include_in_schema: bool | EmptyType = Empty, - operation_class: type[Operation] = Operation, - operation_id: str | OperationIDCreator | None = None, - raises: Sequence[type[HTTPException]] | None = None, - response_description: str | None = None, - responses: Mapping[int, ResponseSpec] | None = None, - return_dto: type[AbstractDTO] | None | EmptyType = Empty, - security: Sequence[SecurityRequirement] | None = None, - summary: str | None = None, - tags: Sequence[str] | None = None, - type_decoders: TypeDecodersSequence | None = None, - type_encoders: TypeEncodersMap | None = None, - **kwargs: Any, - ) -> None: - """Initialize ``head``. + Notes: + - A response to a head request cannot include a body. + See: [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD). - Notes: - - A response to a head request cannot include a body. - See: [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD). + Args: + path: A path fragment for the route handler function or a sequence of path fragments. + If not given defaults to ``/`` + after_request: A sync or async function executed before a :class:`Request <.connection.Request>` is passed + to any route handler. If this function returns a value, the request will not reach the route handler, + and instead this value will be used. + after_response: A sync or async function called after the response has been awaited. It receives the + :class:`Request <.connection.Request>` object and should not return any values. + background: A :class:`BackgroundTask <.background_tasks.BackgroundTask>` instance or + :class:`BackgroundTasks <.background_tasks.BackgroundTasks>` to execute after the response is finished. + Defaults to ``None``. + before_request: A sync or async function called immediately before calling the route handler. Receives + the :class:`.connection.Request` instance and any non-``None`` return value is used for the response, + bypassing the route handler. + cache: Enables response caching if configured on the application level. Valid values are ``True`` or a number + of seconds (e.g. ``120``) to cache the response. + cache_control: A ``cache-control`` header of type + :class:`CacheControlHeader <.datastructures.CacheControlHeader>` that will be added to the response. + cache_key_builder: A :class:`cache-key builder function <.types.CacheKeyBuilder>`. Allows for customization + of the cache key if caching is configured on the application level. + dependencies: A string keyed mapping of dependency :class:`Provider <.di.Provide>` instances. + dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for (de)serializing and + validation of request data. + etag: An ``etag`` header of type :class:`ETag <.datastructures.ETag>` that will be added to the response. + exception_handlers: A mapping of status codes and/or exception types to handler functions. + guards: A sequence of :class:`Guard <.types.Guard>` callables. + media_type: A member of the :class:`MediaType <.enums.MediaType>` enum or a string with a + valid IANA Media-Type. + middleware: A sequence of :class:`Middleware <.types.Middleware>`. + name: A string identifying the route handler. + opt: A string keyed mapping of arbitrary values that can be accessed in :class:`Guards <.types.Guard>` or + wherever you have access to :class:`Request <.connection.Request>` or :class:`ASGI Scope <.types.Scope>`. + request_class: A custom subclass of :class:`Request <.connection.Request>` to be used as route handler's + default request. + response_class: A custom subclass of :class:`Response <.response.Response>` to be used as route handler's + default response. + response_cookies: A sequence of :class:`Cookie <.datastructures.Cookie>` instances. + response_headers: A string keyed mapping of :class:`ResponseHeader <.datastructures.ResponseHeader>` + instances. + responses: A mapping of additional status codes and a description of their expected content. + This information will be included in the OpenAPI schema + return_dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for serializing + outbound response data. + signature_namespace: A mapping of names to types for use in forward reference resolution during signature modelling. + status_code: An http status code for the response. Defaults to ``200`` for mixed method or ``GET``, ``PUT`` and + ``PATCH``, ``201`` for ``POST`` and ``204`` for ``DELETE``. + sync_to_thread: A boolean dictating whether the handler function will be executed in a worker thread or the + main event loop. This has an effect only for sync handler functions. See using sync handler functions. + content_encoding: A string describing the encoding of the content, e.g. ``base64``. + content_media_type: A string designating the media-type of the content, e.g. ``image/png``. + deprecated: A boolean dictating whether this route should be marked as deprecated in the OpenAPI schema. + description: Text used for the route's schema description section. + include_in_schema: A boolean flag dictating whether the route handler should be documented in the OpenAPI schema. + operation_class: :class:`Operation <.openapi.spec.operation.Operation>` to be used with the route's OpenAPI schema. + operation_id: Either a string or a callable returning a string. An identifier used for the route's schema operationId. + raises: A list of exception classes extending from litestar.HttpException that is used for the OpenAPI documentation. + This list should describe all exceptions raised within the route handler's function/method. The Litestar + ValidationException will be added automatically for the schema if any validation is involved. + response_description: Text used for the route's response schema description section. + security: A sequence of dictionaries that contain information about which security scheme can be used on the endpoint. + summary: Text used for the route's schema summary section. + tags: A sequence of string tags that will be appended to the OpenAPI schema. + type_decoders: A sequence of tuples, each composed of a predicate testing for type identity and a msgspec + hook for deserialization. + type_encoders: A mapping of types to callables that transform them into types supported for serialization. + handler_class: Route handler class instantiated by the decorator - Args: - path: A path fragment for the route handler function or a sequence of path fragments. - If not given defaults to ``/`` - after_request: A sync or async function executed before a :class:`Request <.connection.Request>` is passed - to any route handler. If this function returns a value, the request will not reach the route handler, - and instead this value will be used. - after_response: A sync or async function called after the response has been awaited. It receives the - :class:`Request <.connection.Request>` object and should not return any values. - background: A :class:`BackgroundTask <.background_tasks.BackgroundTask>` instance or - :class:`BackgroundTasks <.background_tasks.BackgroundTasks>` to execute after the response is finished. - Defaults to ``None``. - before_request: A sync or async function called immediately before calling the route handler. Receives - the :class:`.connection.Request` instance and any non-``None`` return value is used for the response, - bypassing the route handler. - cache: Enables response caching if configured on the application level. Valid values are ``True`` or a number - of seconds (e.g. ``120``) to cache the response. - cache_control: A ``cache-control`` header of type - :class:`CacheControlHeader <.datastructures.CacheControlHeader>` that will be added to the response. - cache_key_builder: A :class:`cache-key builder function <.types.CacheKeyBuilder>`. Allows for customization - of the cache key if caching is configured on the application level. - dependencies: A string keyed mapping of dependency :class:`Provider <.di.Provide>` instances. - dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for (de)serializing and - validation of request data. - etag: An ``etag`` header of type :class:`ETag <.datastructures.ETag>` that will be added to the response. - exception_handlers: A mapping of status codes and/or exception types to handler functions. - guards: A sequence of :class:`Guard <.types.Guard>` callables. - http_method: An :class:`http method string <.types.Method>`, a member of the enum - :class:`HttpMethod ` or a list of these that correlates to the methods the - route handler function should handle. - media_type: A member of the :class:`MediaType <.enums.MediaType>` enum or a string with a - valid IANA Media-Type. - middleware: A sequence of :class:`Middleware <.types.Middleware>`. - name: A string identifying the route handler. - opt: A string keyed mapping of arbitrary values that can be accessed in :class:`Guards <.types.Guard>` or - wherever you have access to :class:`Request <.connection.Request>` or :class:`ASGI Scope <.types.Scope>`. - request_class: A custom subclass of :class:`Request <.connection.Request>` to be used as route handler's - default request. - response_class: A custom subclass of :class:`Response <.response.Response>` to be used as route handler's - default response. - response_cookies: A sequence of :class:`Cookie <.datastructures.Cookie>` instances. - response_headers: A string keyed mapping of :class:`ResponseHeader <.datastructures.ResponseHeader>` - instances. - responses: A mapping of additional status codes and a description of their expected content. - This information will be included in the OpenAPI schema - return_dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for serializing - outbound response data. - signature_namespace: A mapping of names to types for use in forward reference resolution during signature modelling. - status_code: An http status code for the response. Defaults to ``200`` for mixed method or ``GET``, ``PUT`` and - ``PATCH``, ``201`` for ``POST`` and ``204`` for ``DELETE``. - sync_to_thread: A boolean dictating whether the handler function will be executed in a worker thread or the - main event loop. This has an effect only for sync handler functions. See using sync handler functions. - content_encoding: A string describing the encoding of the content, e.g. ``base64``. - content_media_type: A string designating the media-type of the content, e.g. ``image/png``. - deprecated: A boolean dictating whether this route should be marked as deprecated in the OpenAPI schema. - description: Text used for the route's schema description section. - include_in_schema: A boolean flag dictating whether the route handler should be documented in the OpenAPI schema. - operation_class: :class:`Operation <.openapi.spec.operation.Operation>` to be used with the route's OpenAPI schema. - operation_id: Either a string or a callable returning a string. An identifier used for the route's schema operationId. - raises: A list of exception classes extending from litestar.HttpException that is used for the OpenAPI documentation. - This list should describe all exceptions raised within the route handler's function/method. The Litestar - ValidationException will be added automatically for the schema if any validation is involved. - response_description: Text used for the route's response schema description section. - security: A sequence of dictionaries that contain information about which security scheme can be used on the endpoint. - summary: Text used for the route's schema summary section. - tags: A sequence of string tags that will be appended to the OpenAPI schema. - type_decoders: A sequence of tuples, each composed of a predicate testing for type identity and a msgspec - hook for deserialization. - type_encoders: A mapping of types to callables that transform them into types supported for serialization. - **kwargs: Any additional kwarg - will be set in the opt dictionary. - """ - if "http_method" in kwargs: - raise ImproperlyConfiguredException(MSG_SEMANTIC_ROUTE_HANDLER_WITH_HTTP) + **kwargs: Any additional kwarg - will be set in the opt dictionary. + """ - super().__init__( + def decorator(fn: AnyCallable) -> HTTPRouteHandler: + return handler_class( + fn=fn, after_request=after_request, after_response=after_response, background=background, @@ -566,147 +565,130 @@ def __init__( **kwargs, ) - def _validate_handler_function(self) -> None: - """Validate the route handler function once it is set by inspecting its return annotations.""" - super()._validate_handler_function() + return decorator - # we allow here File and File because these have special setting for head responses - return_annotation = self.parsed_fn_signature.return_type.annotation - if not ( - return_annotation in {NoneType, None} - or is_class_and_subclass(return_annotation, File) - or is_class_and_subclass(return_annotation, ASGIFileResponse) - ): - raise ImproperlyConfiguredException("A response to a head request should not have a body") +def patch( + path: str | None | Sequence[str] = None, + *, + after_request: AfterRequestHookHandler | None = None, + after_response: AfterResponseHookHandler | None = None, + background: BackgroundTask | BackgroundTasks | None = None, + before_request: BeforeRequestHookHandler | None = None, + cache: bool | int | type[CACHE_FOREVER] = False, + cache_control: CacheControlHeader | None = None, + cache_key_builder: CacheKeyBuilder | None = None, + dependencies: Dependencies | None = None, + dto: type[AbstractDTO] | None | EmptyType = Empty, + etag: ETag | None = None, + exception_handlers: ExceptionHandlersMap | None = None, + guards: Sequence[Guard] | None = None, + media_type: MediaType | str | None = None, + middleware: Sequence[Middleware] | None = None, + name: str | None = None, + opt: Mapping[str, Any] | None = None, + request_class: type[Request] | None = None, + response_class: type[Response] | None = None, + response_cookies: ResponseCookies | None = None, + response_headers: ResponseHeaders | None = None, + return_dto: type[AbstractDTO] | None | EmptyType = Empty, + signature_namespace: Mapping[str, Any] | None = None, + status_code: int | None = None, + sync_to_thread: bool | None = None, + # OpenAPI related attributes + content_encoding: str | None = None, + content_media_type: str | None = None, + deprecated: bool = False, + description: str | None = None, + include_in_schema: bool | EmptyType = Empty, + operation_class: type[Operation] = Operation, + operation_id: str | OperationIDCreator | None = None, + raises: Sequence[type[HTTPException]] | None = None, + response_description: str | None = None, + responses: Mapping[int, ResponseSpec] | None = None, + security: Sequence[SecurityRequirement] | None = None, + summary: str | None = None, + tags: Sequence[str] | None = None, + type_decoders: TypeDecodersSequence | None = None, + type_encoders: TypeEncodersMap | None = None, + handler_class: type[HTTPRouteHandler] = HTTPRouteHandler, + **kwargs: Any, +) -> Callable[[AnyCallable], HTTPRouteHandler]: + """Create an :class:`HTTPRouteHandler` with a ``PATCH`` method. -class patch(HTTPRouteHandler): - """PATCH Route Decorator. + Args: + path: A path fragment for the route handler function or a sequence of path fragments. + If not given defaults to ``/`` + after_request: A sync or async function executed before a :class:`Request <.connection.Request>` is passed + to any route handler. If this function returns a value, the request will not reach the route handler, + and instead this value will be used. + after_response: A sync or async function called after the response has been awaited. It receives the + :class:`Request <.connection.Request>` object and should not return any values. + background: A :class:`BackgroundTask <.background_tasks.BackgroundTask>` instance or + :class:`BackgroundTasks <.background_tasks.BackgroundTasks>` to execute after the response is finished. + Defaults to ``None``. + before_request: A sync or async function called immediately before calling the route handler. Receives + the :class:`.connection.Request` instance and any non-``None`` return value is used for the response, + bypassing the route handler. + cache: Enables response caching if configured on the application level. Valid values are ``True`` or a number + of seconds (e.g. ``120``) to cache the response. + cache_control: A ``cache-control`` header of type + :class:`CacheControlHeader <.datastructures.CacheControlHeader>` that will be added to the response. + cache_key_builder: A :class:`cache-key builder function <.types.CacheKeyBuilder>`. Allows for customization + of the cache key if caching is configured on the application level. + dependencies: A string keyed mapping of dependency :class:`Provider <.di.Provide>` instances. + dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for (de)serializing and + validation of request data. + etag: An ``etag`` header of type :class:`ETag <.datastructures.ETag>` that will be added to the response. + exception_handlers: A mapping of status codes and/or exception types to handler functions. + guards: A sequence of :class:`Guard <.types.Guard>` callables. + media_type: A member of the :class:`MediaType <.enums.MediaType>` enum or a string with a + valid IANA Media-Type. + middleware: A sequence of :class:`Middleware <.types.Middleware>`. + name: A string identifying the route handler. + opt: A string keyed mapping of arbitrary values that can be accessed in :class:`Guards <.types.Guard>` or + wherever you have access to :class:`Request <.connection.Request>` or :class:`ASGI Scope <.types.Scope>`. + request_class: A custom subclass of :class:`Request <.connection.Request>` to be used as route handler's + default request. + response_class: A custom subclass of :class:`Response <.response.Response>` to be used as route handler's + default response. + response_cookies: A sequence of :class:`Cookie <.datastructures.Cookie>` instances. + response_headers: A string keyed mapping of :class:`ResponseHeader <.datastructures.ResponseHeader>` + instances. + responses: A mapping of additional status codes and a description of their expected content. + This information will be included in the OpenAPI schema + return_dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for serializing + outbound response data. + signature_namespace: A mapping of names to types for use in forward reference resolution during signature modelling. + status_code: An http status code for the response. Defaults to ``200`` for mixed method or ``GET``, ``PUT`` and + ``PATCH``, ``201`` for ``POST`` and ``204`` for ``DELETE``. + sync_to_thread: A boolean dictating whether the handler function will be executed in a worker thread or the + main event loop. This has an effect only for sync handler functions. See using sync handler functions. + content_encoding: A string describing the encoding of the content, e.g. ``base64``. + content_media_type: A string designating the media-type of the content, e.g. ``image/png``. + deprecated: A boolean dictating whether this route should be marked as deprecated in the OpenAPI schema. + description: Text used for the route's schema description section. + include_in_schema: A boolean flag dictating whether the route handler should be documented in the OpenAPI schema. + operation_class: :class:`Operation <.openapi.spec.operation.Operation>` to be used with the route's OpenAPI schema. + operation_id: Either a string or a callable returning a string. An identifier used for the route's schema operationId. + raises: A list of exception classes extending from litestar.HttpException that is used for the OpenAPI documentation. + This list should describe all exceptions raised within the route handler's function/method. The Litestar + ValidationException will be added automatically for the schema if any validation is involved. + response_description: Text used for the route's response schema description section. + security: A sequence of dictionaries that contain information about which security scheme can be used on the endpoint. + summary: Text used for the route's schema summary section. + tags: A sequence of string tags that will be appended to the OpenAPI schema. + type_decoders: A sequence of tuples, each composed of a predicate testing for type identity and a msgspec + hook for deserialization. + type_encoders: A mapping of types to callables that transform them into types supported for serialization. + handler_class: Route handler class instantiated by the decorator - Use this decorator to decorate an HTTP handler for PATCH requests. + **kwargs: Any additional kwarg - will be set in the opt dictionary. """ - def __init__( - self, - path: str | None | Sequence[str] = None, - *, - after_request: AfterRequestHookHandler | None = None, - after_response: AfterResponseHookHandler | None = None, - background: BackgroundTask | BackgroundTasks | None = None, - before_request: BeforeRequestHookHandler | None = None, - cache: bool | int | type[CACHE_FOREVER] = False, - cache_control: CacheControlHeader | None = None, - cache_key_builder: CacheKeyBuilder | None = None, - dependencies: Dependencies | None = None, - dto: type[AbstractDTO] | None | EmptyType = Empty, - etag: ETag | None = None, - exception_handlers: ExceptionHandlersMap | None = None, - guards: Sequence[Guard] | None = None, - media_type: MediaType | str | None = None, - middleware: Sequence[Middleware] | None = None, - name: str | None = None, - opt: Mapping[str, Any] | None = None, - request_class: type[Request] | None = None, - response_class: type[Response] | None = None, - response_cookies: ResponseCookies | None = None, - response_headers: ResponseHeaders | None = None, - return_dto: type[AbstractDTO] | None | EmptyType = Empty, - signature_namespace: Mapping[str, Any] | None = None, - status_code: int | None = None, - sync_to_thread: bool | None = None, - # OpenAPI related attributes - content_encoding: str | None = None, - content_media_type: str | None = None, - deprecated: bool = False, - description: str | None = None, - include_in_schema: bool | EmptyType = Empty, - operation_class: type[Operation] = Operation, - operation_id: str | OperationIDCreator | None = None, - raises: Sequence[type[HTTPException]] | None = None, - response_description: str | None = None, - responses: Mapping[int, ResponseSpec] | None = None, - security: Sequence[SecurityRequirement] | None = None, - summary: str | None = None, - tags: Sequence[str] | None = None, - type_decoders: TypeDecodersSequence | None = None, - type_encoders: TypeEncodersMap | None = None, - **kwargs: Any, - ) -> None: - """Initialize ``patch``. - - Args: - path: A path fragment for the route handler function or a sequence of path fragments. - If not given defaults to ``/`` - after_request: A sync or async function executed before a :class:`Request <.connection.Request>` is passed - to any route handler. If this function returns a value, the request will not reach the route handler, - and instead this value will be used. - after_response: A sync or async function called after the response has been awaited. It receives the - :class:`Request <.connection.Request>` object and should not return any values. - background: A :class:`BackgroundTask <.background_tasks.BackgroundTask>` instance or - :class:`BackgroundTasks <.background_tasks.BackgroundTasks>` to execute after the response is finished. - Defaults to ``None``. - before_request: A sync or async function called immediately before calling the route handler. Receives - the :class:`.connection.Request` instance and any non-``None`` return value is used for the response, - bypassing the route handler. - cache: Enables response caching if configured on the application level. Valid values are ``True`` or a number - of seconds (e.g. ``120``) to cache the response. - cache_control: A ``cache-control`` header of type - :class:`CacheControlHeader <.datastructures.CacheControlHeader>` that will be added to the response. - cache_key_builder: A :class:`cache-key builder function <.types.CacheKeyBuilder>`. Allows for customization - of the cache key if caching is configured on the application level. - dependencies: A string keyed mapping of dependency :class:`Provider <.di.Provide>` instances. - dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for (de)serializing and - validation of request data. - etag: An ``etag`` header of type :class:`ETag <.datastructures.ETag>` that will be added to the response. - exception_handlers: A mapping of status codes and/or exception types to handler functions. - guards: A sequence of :class:`Guard <.types.Guard>` callables. - http_method: An :class:`http method string <.types.Method>`, a member of the enum - :class:`HttpMethod ` or a list of these that correlates to the methods the - route handler function should handle. - media_type: A member of the :class:`MediaType <.enums.MediaType>` enum or a string with a - valid IANA Media-Type. - middleware: A sequence of :class:`Middleware <.types.Middleware>`. - name: A string identifying the route handler. - opt: A string keyed mapping of arbitrary values that can be accessed in :class:`Guards <.types.Guard>` or - wherever you have access to :class:`Request <.connection.Request>` or :class:`ASGI Scope <.types.Scope>`. - request_class: A custom subclass of :class:`Request <.connection.Request>` to be used as route handler's - default request. - response_class: A custom subclass of :class:`Response <.response.Response>` to be used as route handler's - default response. - response_cookies: A sequence of :class:`Cookie <.datastructures.Cookie>` instances. - response_headers: A string keyed mapping of :class:`ResponseHeader <.datastructures.ResponseHeader>` - instances. - responses: A mapping of additional status codes and a description of their expected content. - This information will be included in the OpenAPI schema - return_dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for serializing - outbound response data. - signature_namespace: A mapping of names to types for use in forward reference resolution during signature modelling. - status_code: An http status code for the response. Defaults to ``200`` for mixed method or ``GET``, ``PUT`` and - ``PATCH``, ``201`` for ``POST`` and ``204`` for ``DELETE``. - sync_to_thread: A boolean dictating whether the handler function will be executed in a worker thread or the - main event loop. This has an effect only for sync handler functions. See using sync handler functions. - content_encoding: A string describing the encoding of the content, e.g. ``base64``. - content_media_type: A string designating the media-type of the content, e.g. ``image/png``. - deprecated: A boolean dictating whether this route should be marked as deprecated in the OpenAPI schema. - description: Text used for the route's schema description section. - include_in_schema: A boolean flag dictating whether the route handler should be documented in the OpenAPI schema. - operation_class: :class:`Operation <.openapi.spec.operation.Operation>` to be used with the route's OpenAPI schema. - operation_id: Either a string or a callable returning a string. An identifier used for the route's schema operationId. - raises: A list of exception classes extending from litestar.HttpException that is used for the OpenAPI documentation. - This list should describe all exceptions raised within the route handler's function/method. The Litestar - ValidationException will be added automatically for the schema if any validation is involved. - response_description: Text used for the route's response schema description section. - security: A sequence of dictionaries that contain information about which security scheme can be used on the endpoint. - summary: Text used for the route's schema summary section. - tags: A sequence of string tags that will be appended to the OpenAPI schema. - type_decoders: A sequence of tuples, each composed of a predicate testing for type identity and a msgspec - hook for deserialization. - type_encoders: A mapping of types to callables that transform them into types supported for serialization. - **kwargs: Any additional kwarg - will be set in the opt dictionary. - """ - if "http_method" in kwargs: - raise ImproperlyConfiguredException(MSG_SEMANTIC_ROUTE_HANDLER_WITH_HTTP) - super().__init__( + def decorator(fn: AnyCallable) -> HTTPRouteHandler: + return handler_class( + fn=fn, after_request=after_request, after_response=after_response, background=background, @@ -751,134 +733,130 @@ def __init__( **kwargs, ) + return decorator -class post(HTTPRouteHandler): - """POST Route Decorator. - Use this decorator to decorate an HTTP handler for POST requests. - """ +def post( + path: str | None | Sequence[str] = None, + *, + after_request: AfterRequestHookHandler | None = None, + after_response: AfterResponseHookHandler | None = None, + background: BackgroundTask | BackgroundTasks | None = None, + before_request: BeforeRequestHookHandler | None = None, + cache: bool | int | type[CACHE_FOREVER] = False, + cache_control: CacheControlHeader | None = None, + cache_key_builder: CacheKeyBuilder | None = None, + dependencies: Dependencies | None = None, + dto: type[AbstractDTO] | None | EmptyType = Empty, + etag: ETag | None = None, + exception_handlers: ExceptionHandlersMap | None = None, + guards: Sequence[Guard] | None = None, + media_type: MediaType | str | None = None, + middleware: Sequence[Middleware] | None = None, + name: str | None = None, + opt: Mapping[str, Any] | None = None, + request_class: type[Request] | None = None, + response_class: type[Response] | None = None, + response_cookies: ResponseCookies | None = None, + response_headers: ResponseHeaders | None = None, + return_dto: type[AbstractDTO] | None | EmptyType = Empty, + signature_namespace: Mapping[str, Any] | None = None, + status_code: int | None = None, + sync_to_thread: bool | None = None, + # OpenAPI related attributes + content_encoding: str | None = None, + content_media_type: str | None = None, + deprecated: bool = False, + description: str | None = None, + include_in_schema: bool | EmptyType = Empty, + operation_class: type[Operation] = Operation, + operation_id: str | OperationIDCreator | None = None, + raises: Sequence[type[HTTPException]] | None = None, + response_description: str | None = None, + responses: Mapping[int, ResponseSpec] | None = None, + security: Sequence[SecurityRequirement] | None = None, + summary: str | None = None, + tags: Sequence[str] | None = None, + type_decoders: TypeDecodersSequence | None = None, + type_encoders: TypeEncodersMap | None = None, + handler_class: type[HTTPRouteHandler] = HTTPRouteHandler, + **kwargs: Any, +) -> Callable[[AnyCallable], HTTPRouteHandler]: + """Create an :class:`HTTPRouteHandler` with a ``POST`` method. + + Args: + path: A path fragment for the route handler function or a sequence of path fragments. + If not given defaults to ``/`` + after_request: A sync or async function executed before a :class:`Request <.connection.Request>` is passed + to any route handler. If this function returns a value, the request will not reach the route handler, + and instead this value will be used. + after_response: A sync or async function called after the response has been awaited. It receives the + :class:`Request <.connection.Request>` object and should not return any values. + background: A :class:`BackgroundTask <.background_tasks.BackgroundTask>` instance or + :class:`BackgroundTasks <.background_tasks.BackgroundTasks>` to execute after the response is finished. + Defaults to ``None``. + before_request: A sync or async function called immediately before calling the route handler. Receives + the :class:`.connection.Request` instance and any non-``None`` return value is used for the response, + bypassing the route handler. + cache: Enables response caching if configured on the application level. Valid values are ``True`` or a number + of seconds (e.g. ``120``) to cache the response. + cache_control: A ``cache-control`` header of type + :class:`CacheControlHeader <.datastructures.CacheControlHeader>` that will be added to the response. + cache_key_builder: A :class:`cache-key builder function <.types.CacheKeyBuilder>`. Allows for customization + of the cache key if caching is configured on the application level. + dependencies: A string keyed mapping of dependency :class:`Provider <.di.Provide>` instances. + dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for (de)serializing and + validation of request data. + etag: An ``etag`` header of type :class:`ETag <.datastructures.ETag>` that will be added to the response. + exception_handlers: A mapping of status codes and/or exception types to handler functions. + guards: A sequence of :class:`Guard <.types.Guard>` callables. + media_type: A member of the :class:`MediaType <.enums.MediaType>` enum or a string with a + valid IANA Media-Type. + middleware: A sequence of :class:`Middleware <.types.Middleware>`. + name: A string identifying the route handler. + opt: A string keyed mapping of arbitrary values that can be accessed in :class:`Guards <.types.Guard>` or + wherever you have access to :class:`Request <.connection.Request>` or :class:`ASGI Scope <.types.Scope>`. + request_class: A custom subclass of :class:`Request <.connection.Request>` to be used as route handler's + default request. + response_class: A custom subclass of :class:`Response <.response.Response>` to be used as route handler's + default response. + response_cookies: A sequence of :class:`Cookie <.datastructures.Cookie>` instances. + response_headers: A string keyed mapping of :class:`ResponseHeader <.datastructures.ResponseHeader>` + instances. + responses: A mapping of additional status codes and a description of their expected content. + This information will be included in the OpenAPI schema + return_dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for serializing + outbound response data. + signature_namespace: A mapping of names to types for use in forward reference resolution during signature modelling. + status_code: An http status code for the response. Defaults to ``200`` for mixed method or ``GET``, ``PUT`` and + ``PATCH``, ``201`` for ``POST`` and ``204`` for ``DELETE``. + sync_to_thread: A boolean dictating whether the handler function will be executed in a worker thread or the + main event loop. This has an effect only for sync handler functions. See using sync handler functions. + content_encoding: A string describing the encoding of the content, e.g. ``base64``. + content_media_type: A string designating the media-type of the content, e.g. ``image/png``. + deprecated: A boolean dictating whether this route should be marked as deprecated in the OpenAPI schema. + description: Text used for the route's schema description section. + include_in_schema: A boolean flag dictating whether the route handler should be documented in the OpenAPI schema. + operation_class: :class:`Operation <.openapi.spec.operation.Operation>` to be used with the route's OpenAPI schema. + operation_id: Either a string or a callable returning a string. An identifier used for the route's schema operationId. + raises: A list of exception classes extending from litestar.HttpException that is used for the OpenAPI documentation. + This list should describe all exceptions raised within the route handler's function/method. The Litestar + ValidationException will be added automatically for the schema if any validation is involved. + response_description: Text used for the route's response schema description section. + security: A sequence of dictionaries that contain information about which security scheme can be used on the endpoint. + summary: Text used for the route's schema summary section. + tags: A sequence of string tags that will be appended to the OpenAPI schema. + type_decoders: A sequence of tuples, each composed of a predicate testing for type identity and a msgspec + hook for deserialization. + type_encoders: A mapping of types to callables that transform them into types supported for serialization. + handler_class: Route handler class instantiated by the decorator - def __init__( - self, - path: str | None | Sequence[str] = None, - *, - after_request: AfterRequestHookHandler | None = None, - after_response: AfterResponseHookHandler | None = None, - background: BackgroundTask | BackgroundTasks | None = None, - before_request: BeforeRequestHookHandler | None = None, - cache: bool | int | type[CACHE_FOREVER] = False, - cache_control: CacheControlHeader | None = None, - cache_key_builder: CacheKeyBuilder | None = None, - dependencies: Dependencies | None = None, - dto: type[AbstractDTO] | None | EmptyType = Empty, - etag: ETag | None = None, - exception_handlers: ExceptionHandlersMap | None = None, - guards: Sequence[Guard] | None = None, - media_type: MediaType | str | None = None, - middleware: Sequence[Middleware] | None = None, - name: str | None = None, - opt: Mapping[str, Any] | None = None, - request_class: type[Request] | None = None, - response_class: type[Response] | None = None, - response_cookies: ResponseCookies | None = None, - response_headers: ResponseHeaders | None = None, - return_dto: type[AbstractDTO] | None | EmptyType = Empty, - signature_namespace: Mapping[str, Any] | None = None, - status_code: int | None = None, - sync_to_thread: bool | None = None, - # OpenAPI related attributes - content_encoding: str | None = None, - content_media_type: str | None = None, - deprecated: bool = False, - description: str | None = None, - include_in_schema: bool | EmptyType = Empty, - operation_class: type[Operation] = Operation, - operation_id: str | OperationIDCreator | None = None, - raises: Sequence[type[HTTPException]] | None = None, - response_description: str | None = None, - responses: Mapping[int, ResponseSpec] | None = None, - security: Sequence[SecurityRequirement] | None = None, - summary: str | None = None, - tags: Sequence[str] | None = None, - type_decoders: TypeDecodersSequence | None = None, - type_encoders: TypeEncodersMap | None = None, - **kwargs: Any, - ) -> None: - """Initialize ``post`` + **kwargs: Any additional kwarg - will be set in the opt dictionary. + """ - Args: - path: A path fragment for the route handler function or a sequence of path fragments. - If not given defaults to ``/`` - after_request: A sync or async function executed before a :class:`Request <.connection.Request>` is passed - to any route handler. If this function returns a value, the request will not reach the route handler, - and instead this value will be used. - after_response: A sync or async function called after the response has been awaited. It receives the - :class:`Request <.connection.Request>` object and should not return any values. - background: A :class:`BackgroundTask <.background_tasks.BackgroundTask>` instance or - :class:`BackgroundTasks <.background_tasks.BackgroundTasks>` to execute after the response is finished. - Defaults to ``None``. - before_request: A sync or async function called immediately before calling the route handler. Receives - the :class:`.connection.Request` instance and any non-``None`` return value is used for the response, - bypassing the route handler. - cache: Enables response caching if configured on the application level. Valid values are ``True`` or a number - of seconds (e.g. ``120``) to cache the response. - cache_control: A ``cache-control`` header of type - :class:`CacheControlHeader <.datastructures.CacheControlHeader>` that will be added to the response. - cache_key_builder: A :class:`cache-key builder function <.types.CacheKeyBuilder>`. Allows for customization - of the cache key if caching is configured on the application level. - dependencies: A string keyed mapping of dependency :class:`Provider <.di.Provide>` instances. - dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for (de)serializing and - validation of request data. - etag: An ``etag`` header of type :class:`ETag <.datastructures.ETag>` that will be added to the response. - exception_handlers: A mapping of status codes and/or exception types to handler functions. - guards: A sequence of :class:`Guard <.types.Guard>` callables. - http_method: An :class:`http method string <.types.Method>`, a member of the enum - :class:`HttpMethod ` or a list of these that correlates to the methods the - route handler function should handle. - media_type: A member of the :class:`MediaType <.enums.MediaType>` enum or a string with a - valid IANA Media-Type. - middleware: A sequence of :class:`Middleware <.types.Middleware>`. - name: A string identifying the route handler. - opt: A string keyed mapping of arbitrary values that can be accessed in :class:`Guards <.types.Guard>` or - wherever you have access to :class:`Request <.connection.Request>` or :class:`ASGI Scope <.types.Scope>`. - request_class: A custom subclass of :class:`Request <.connection.Request>` to be used as route handler's - default request. - response_class: A custom subclass of :class:`Response <.response.Response>` to be used as route handler's - default response. - response_cookies: A sequence of :class:`Cookie <.datastructures.Cookie>` instances. - response_headers: A string keyed mapping of :class:`ResponseHeader <.datastructures.ResponseHeader>` - instances. - responses: A mapping of additional status codes and a description of their expected content. - This information will be included in the OpenAPI schema - return_dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for serializing - outbound response data. - signature_namespace: A mapping of names to types for use in forward reference resolution during signature modelling. - status_code: An http status code for the response. Defaults to ``200`` for mixed method or ``GET``, ``PUT`` and - ``PATCH``, ``201`` for ``POST`` and ``204`` for ``DELETE``. - sync_to_thread: A boolean dictating whether the handler function will be executed in a worker thread or the - main event loop. This has an effect only for sync handler functions. See using sync handler functions. - content_encoding: A string describing the encoding of the content, e.g. ``base64``. - content_media_type: A string designating the media-type of the content, e.g. ``image/png``. - deprecated: A boolean dictating whether this route should be marked as deprecated in the OpenAPI schema. - description: Text used for the route's schema description section. - include_in_schema: A boolean flag dictating whether the route handler should be documented in the OpenAPI schema. - operation_class: :class:`Operation <.openapi.spec.operation.Operation>` to be used with the route's OpenAPI schema. - operation_id: Either a string or a callable returning a string. An identifier used for the route's schema operationId. - raises: A list of exception classes extending from litestar.HttpException that is used for the OpenAPI documentation. - This list should describe all exceptions raised within the route handler's function/method. The Litestar - ValidationException will be added automatically for the schema if any validation is involved. - response_description: Text used for the route's response schema description section. - security: A sequence of dictionaries that contain information about which security scheme can be used on the endpoint. - summary: Text used for the route's schema summary section. - tags: A sequence of string tags that will be appended to the OpenAPI schema. - type_decoders: A sequence of tuples, each composed of a predicate testing for type identity and a msgspec - hook for deserialization. - type_encoders: A mapping of types to callables that transform them into types supported for serialization. - **kwargs: Any additional kwarg - will be set in the opt dictionary. - """ - if "http_method" in kwargs: - raise ImproperlyConfiguredException(MSG_SEMANTIC_ROUTE_HANDLER_WITH_HTTP) - super().__init__( + def decorator(fn: AnyCallable) -> HTTPRouteHandler: + return handler_class( + fn=fn, after_request=after_request, after_response=after_response, background=background, @@ -923,134 +901,130 @@ def __init__( **kwargs, ) + return decorator -class put(HTTPRouteHandler): - """PUT Route Decorator. - Use this decorator to decorate an HTTP handler for PUT requests. - """ +def put( + path: str | None | Sequence[str] = None, + *, + after_request: AfterRequestHookHandler | None = None, + after_response: AfterResponseHookHandler | None = None, + background: BackgroundTask | BackgroundTasks | None = None, + before_request: BeforeRequestHookHandler | None = None, + cache: bool | int | type[CACHE_FOREVER] = False, + cache_control: CacheControlHeader | None = None, + cache_key_builder: CacheKeyBuilder | None = None, + dependencies: Dependencies | None = None, + dto: type[AbstractDTO] | None | EmptyType = Empty, + etag: ETag | None = None, + exception_handlers: ExceptionHandlersMap | None = None, + guards: Sequence[Guard] | None = None, + media_type: MediaType | str | None = None, + middleware: Sequence[Middleware] | None = None, + name: str | None = None, + opt: Mapping[str, Any] | None = None, + request_class: type[Request] | None = None, + response_class: type[Response] | None = None, + response_cookies: ResponseCookies | None = None, + response_headers: ResponseHeaders | None = None, + return_dto: type[AbstractDTO] | None | EmptyType = Empty, + signature_namespace: Mapping[str, Any] | None = None, + status_code: int | None = None, + sync_to_thread: bool | None = None, + # OpenAPI related attributes + content_encoding: str | None = None, + content_media_type: str | None = None, + deprecated: bool = False, + description: str | None = None, + include_in_schema: bool | EmptyType = Empty, + operation_class: type[Operation] = Operation, + operation_id: str | OperationIDCreator | None = None, + raises: Sequence[type[HTTPException]] | None = None, + response_description: str | None = None, + responses: Mapping[int, ResponseSpec] | None = None, + security: Sequence[SecurityRequirement] | None = None, + summary: str | None = None, + tags: Sequence[str] | None = None, + type_decoders: TypeDecodersSequence | None = None, + type_encoders: TypeEncodersMap | None = None, + handler_class: type[HTTPRouteHandler] = HTTPRouteHandler, + **kwargs: Any, +) -> Callable[[AnyCallable], HTTPRouteHandler]: + """Create an :class:`HTTPRouteHandler` with a ``PUT`` method. - def __init__( - self, - path: str | None | Sequence[str] = None, - *, - after_request: AfterRequestHookHandler | None = None, - after_response: AfterResponseHookHandler | None = None, - background: BackgroundTask | BackgroundTasks | None = None, - before_request: BeforeRequestHookHandler | None = None, - cache: bool | int | type[CACHE_FOREVER] = False, - cache_control: CacheControlHeader | None = None, - cache_key_builder: CacheKeyBuilder | None = None, - dependencies: Dependencies | None = None, - dto: type[AbstractDTO] | None | EmptyType = Empty, - etag: ETag | None = None, - exception_handlers: ExceptionHandlersMap | None = None, - guards: Sequence[Guard] | None = None, - media_type: MediaType | str | None = None, - middleware: Sequence[Middleware] | None = None, - name: str | None = None, - opt: Mapping[str, Any] | None = None, - request_class: type[Request] | None = None, - response_class: type[Response] | None = None, - response_cookies: ResponseCookies | None = None, - response_headers: ResponseHeaders | None = None, - return_dto: type[AbstractDTO] | None | EmptyType = Empty, - signature_namespace: Mapping[str, Any] | None = None, - status_code: int | None = None, - sync_to_thread: bool | None = None, - # OpenAPI related attributes - content_encoding: str | None = None, - content_media_type: str | None = None, - deprecated: bool = False, - description: str | None = None, - include_in_schema: bool | EmptyType = Empty, - operation_class: type[Operation] = Operation, - operation_id: str | OperationIDCreator | None = None, - raises: Sequence[type[HTTPException]] | None = None, - response_description: str | None = None, - responses: Mapping[int, ResponseSpec] | None = None, - security: Sequence[SecurityRequirement] | None = None, - summary: str | None = None, - tags: Sequence[str] | None = None, - type_decoders: TypeDecodersSequence | None = None, - type_encoders: TypeEncodersMap | None = None, - **kwargs: Any, - ) -> None: - """Initialize ``put`` + Args: + path: A path fragment for the route handler function or a sequence of path fragments. + If not given defaults to ``/`` + after_request: A sync or async function executed before a :class:`Request <.connection.Request>` is passed + to any route handler. If this function returns a value, the request will not reach the route handler, + and instead this value will be used. + after_response: A sync or async function called after the response has been awaited. It receives the + :class:`Request <.connection.Request>` object and should not return any values. + background: A :class:`BackgroundTask <.background_tasks.BackgroundTask>` instance or + :class:`BackgroundTasks <.background_tasks.BackgroundTasks>` to execute after the response is finished. + Defaults to ``None``. + before_request: A sync or async function called immediately before calling the route handler. Receives + the :class:`.connection.Request` instance and any non-``None`` return value is used for the response, + bypassing the route handler. + cache: Enables response caching if configured on the application level. Valid values are ``True`` or a number + of seconds (e.g. ``120``) to cache the response. + cache_control: A ``cache-control`` header of type + :class:`CacheControlHeader <.datastructures.CacheControlHeader>` that will be added to the response. + cache_key_builder: A :class:`cache-key builder function <.types.CacheKeyBuilder>`. Allows for customization + of the cache key if caching is configured on the application level. + dependencies: A string keyed mapping of dependency :class:`Provider <.di.Provide>` instances. + dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for (de)serializing and + validation of request data. + etag: An ``etag`` header of type :class:`ETag <.datastructures.ETag>` that will be added to the response. + exception_handlers: A mapping of status codes and/or exception types to handler functions. + guards: A sequence of :class:`Guard <.types.Guard>` callables. + media_type: A member of the :class:`MediaType <.enums.MediaType>` enum or a string with a + valid IANA Media-Type. + middleware: A sequence of :class:`Middleware <.types.Middleware>`. + name: A string identifying the route handler. + opt: A string keyed mapping of arbitrary values that can be accessed in :class:`Guards <.types.Guard>` or + wherever you have access to :class:`Request <.connection.Request>` or :class:`ASGI Scope <.types.Scope>`. + request_class: A custom subclass of :class:`Request <.connection.Request>` to be used as route handler's + default request. + response_class: A custom subclass of :class:`Response <.response.Response>` to be used as route handler's + default response. + response_cookies: A sequence of :class:`Cookie <.datastructures.Cookie>` instances. + response_headers: A string keyed mapping of :class:`ResponseHeader <.datastructures.ResponseHeader>` + instances. + responses: A mapping of additional status codes and a description of their expected content. + This information will be included in the OpenAPI schema + return_dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for serializing + outbound response data. + signature_namespace: A mapping of names to types for use in forward reference resolution during signature modelling. + status_code: An http status code for the response. Defaults to ``200`` for mixed method or ``GET``, ``PUT`` and + ``PATCH``, ``201`` for ``POST`` and ``204`` for ``DELETE``. + sync_to_thread: A boolean dictating whether the handler function will be executed in a worker thread or the + main event loop. This has an effect only for sync handler functions. See using sync handler functions. + content_encoding: A string describing the encoding of the content, e.g. ``base64``. + content_media_type: A string designating the media-type of the content, e.g. ``image/png``. + deprecated: A boolean dictating whether this route should be marked as deprecated in the OpenAPI schema. + description: Text used for the route's schema description section. + include_in_schema: A boolean flag dictating whether the route handler should be documented in the OpenAPI schema. + operation_class: :class:`Operation <.openapi.spec.operation.Operation>` to be used with the route's OpenAPI schema. + operation_id: Either a string or a callable returning a string. An identifier used for the route's schema operationId. + raises: A list of exception classes extending from litestar.HttpException that is used for the OpenAPI documentation. + This list should describe all exceptions raised within the route handler's function/method. The Litestar + ValidationException will be added automatically for the schema if any validation is involved. + response_description: Text used for the route's response schema description section. + security: A sequence of dictionaries that contain information about which security scheme can be used on the endpoint. + summary: Text used for the route's schema summary section. + tags: A sequence of string tags that will be appended to the OpenAPI schema. + type_decoders: A sequence of tuples, each composed of a predicate testing for type identity and a msgspec + hook for deserialization. + type_encoders: A mapping of types to callables that transform them into types supported for serialization. + handler_class: Route handler class instantiated by the decorator - Args: - path: A path fragment for the route handler function or a sequence of path fragments. - If not given defaults to ``/`` - after_request: A sync or async function executed before a :class:`Request <.connection.Request>` is passed - to any route handler. If this function returns a value, the request will not reach the route handler, - and instead this value will be used. - after_response: A sync or async function called after the response has been awaited. It receives the - :class:`Request <.connection.Request>` object and should not return any values. - background: A :class:`BackgroundTask <.background_tasks.BackgroundTask>` instance or - :class:`BackgroundTasks <.background_tasks.BackgroundTasks>` to execute after the response is finished. - Defaults to ``None``. - before_request: A sync or async function called immediately before calling the route handler. Receives - the :class:`.connection.Request` instance and any non-``None`` return value is used for the response, - bypassing the route handler. - cache: Enables response caching if configured on the application level. Valid values are ``True`` or a number - of seconds (e.g. ``120``) to cache the response. - cache_control: A ``cache-control`` header of type - :class:`CacheControlHeader <.datastructures.CacheControlHeader>` that will be added to the response. - cache_key_builder: A :class:`cache-key builder function <.types.CacheKeyBuilder>`. Allows for customization - of the cache key if caching is configured on the application level. - dependencies: A string keyed mapping of dependency :class:`Provider <.di.Provide>` instances. - dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for (de)serializing and - validation of request data. - etag: An ``etag`` header of type :class:`ETag <.datastructures.ETag>` that will be added to the response. - exception_handlers: A mapping of status codes and/or exception types to handler functions. - guards: A sequence of :class:`Guard <.types.Guard>` callables. - http_method: An :class:`http method string <.types.Method>`, a member of the enum - :class:`HttpMethod ` or a list of these that correlates to the methods the - route handler function should handle. - media_type: A member of the :class:`MediaType <.enums.MediaType>` enum or a string with a - valid IANA Media-Type. - middleware: A sequence of :class:`Middleware <.types.Middleware>`. - name: A string identifying the route handler. - opt: A string keyed mapping of arbitrary values that can be accessed in :class:`Guards <.types.Guard>` or - wherever you have access to :class:`Request <.connection.Request>` or :class:`ASGI Scope <.types.Scope>`. - request_class: A custom subclass of :class:`Request <.connection.Request>` to be used as route handler's - default request. - response_class: A custom subclass of :class:`Response <.response.Response>` to be used as route handler's - default response. - response_cookies: A sequence of :class:`Cookie <.datastructures.Cookie>` instances. - response_headers: A string keyed mapping of :class:`ResponseHeader <.datastructures.ResponseHeader>` - instances. - responses: A mapping of additional status codes and a description of their expected content. - This information will be included in the OpenAPI schema - return_dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for serializing - outbound response data. - signature_namespace: A mapping of names to types for use in forward reference resolution during signature modelling. - status_code: An http status code for the response. Defaults to ``200`` for mixed method or ``GET``, ``PUT`` and - ``PATCH``, ``201`` for ``POST`` and ``204`` for ``DELETE``. - sync_to_thread: A boolean dictating whether the handler function will be executed in a worker thread or the - main event loop. This has an effect only for sync handler functions. See using sync handler functions. - content_encoding: A string describing the encoding of the content, e.g. ``base64``. - content_media_type: A string designating the media-type of the content, e.g. ``image/png``. - deprecated: A boolean dictating whether this route should be marked as deprecated in the OpenAPI schema. - description: Text used for the route's schema description section. - include_in_schema: A boolean flag dictating whether the route handler should be documented in the OpenAPI schema. - operation_class: :class:`Operation <.openapi.spec.operation.Operation>` to be used with the route's OpenAPI schema. - operation_id: Either a string or a callable returning a string. An identifier used for the route's schema operationId. - raises: A list of exception classes extending from litestar.HttpException that is used for the OpenAPI documentation. - This list should describe all exceptions raised within the route handler's function/method. The Litestar - ValidationException will be added automatically for the schema if any validation is involved. - response_description: Text used for the route's response schema description section. - security: A sequence of dictionaries that contain information about which security scheme can be used on the endpoint. - summary: Text used for the route's schema summary section. - tags: A sequence of string tags that will be appended to the OpenAPI schema. - type_decoders: A sequence of tuples, each composed of a predicate testing for type identity and a msgspec - hook for deserialization. - type_encoders: A mapping of types to callables that transform them into types supported for serialization. - **kwargs: Any additional kwarg - will be set in the opt dictionary. - """ - if "http_method" in kwargs: - raise ImproperlyConfiguredException(MSG_SEMANTIC_ROUTE_HANDLER_WITH_HTTP) - super().__init__( + **kwargs: Any additional kwarg - will be set in the opt dictionary. + """ + + def decorator(fn: AnyCallable) -> HTTPRouteHandler: + return handler_class( + fn=fn, after_request=after_request, after_response=after_response, background=background, @@ -1094,3 +1068,172 @@ def __init__( type_encoders=type_encoders, **kwargs, ) + + return decorator + + +def delete( + path: str | None | Sequence[str] = None, + *, + after_request: AfterRequestHookHandler | None = None, + after_response: AfterResponseHookHandler | None = None, + background: BackgroundTask | BackgroundTasks | None = None, + before_request: BeforeRequestHookHandler | None = None, + cache: bool | int | type[CACHE_FOREVER] = False, + cache_control: CacheControlHeader | None = None, + cache_key_builder: CacheKeyBuilder | None = None, + dependencies: Dependencies | None = None, + dto: type[AbstractDTO] | None | EmptyType = Empty, + etag: ETag | None = None, + exception_handlers: ExceptionHandlersMap | None = None, + guards: Sequence[Guard] | None = None, + media_type: MediaType | str | None = None, + middleware: Sequence[Middleware] | None = None, + name: str | None = None, + opt: Mapping[str, Any] | None = None, + request_class: type[Request] | None = None, + response_class: type[Response] | None = None, + response_cookies: ResponseCookies | None = None, + response_headers: ResponseHeaders | None = None, + return_dto: type[AbstractDTO] | None | EmptyType = Empty, + signature_namespace: Mapping[str, Any] | None = None, + status_code: int | None = None, + sync_to_thread: bool | None = None, + # OpenAPI related attributes + content_encoding: str | None = None, + content_media_type: str | None = None, + deprecated: bool = False, + description: str | None = None, + include_in_schema: bool | EmptyType = Empty, + operation_class: type[Operation] = Operation, + operation_id: str | OperationIDCreator | None = None, + raises: Sequence[type[HTTPException]] | None = None, + response_description: str | None = None, + responses: Mapping[int, ResponseSpec] | None = None, + security: Sequence[SecurityRequirement] | None = None, + summary: str | None = None, + tags: Sequence[str] | None = None, + type_decoders: TypeDecodersSequence | None = None, + type_encoders: TypeEncodersMap | None = None, + handler_class: type[HTTPRouteHandler] = HTTPRouteHandler, + **kwargs: Any, +) -> Callable[[AnyCallable], HTTPRouteHandler]: + """Create an :class:`HTTPRouteHandler` with a ``DELETE`` method. + + Args: + path: A path fragment for the route handler function or a sequence of path fragments. + If not given defaults to ``/`` + after_request: A sync or async function executed before a :class:`Request <.connection.Request>` is passed + to any route handler. If this function returns a value, the request will not reach the route handler, + and instead this value will be used. + after_response: A sync or async function called after the response has been awaited. It receives the + :class:`Request <.connection.Request>` object and should not return any values. + background: A :class:`BackgroundTask <.background_tasks.BackgroundTask>` instance or + :class:`BackgroundTasks <.background_tasks.BackgroundTasks>` to execute after the response is finished. + Defaults to ``None``. + before_request: A sync or async function called immediately before calling the route handler. Receives + the :class:`.connection.Request` instance and any non-``None`` return value is used for the response, + bypassing the route handler. + cache: Enables response caching if configured on the application level. Valid values are ``True`` or a number + of seconds (e.g. ``120``) to cache the response. + cache_control: A ``cache-control`` header of type + :class:`CacheControlHeader <.datastructures.CacheControlHeader>` that will be added to the response. + cache_key_builder: A :class:`cache-key builder function <.types.CacheKeyBuilder>`. Allows for customization + of the cache key if caching is configured on the application level. + dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for (de)serializing and + validation of request data. + dependencies: A string keyed mapping of dependency :class:`Provider <.di.Provide>` instances. + etag: An ``etag`` header of type :class:`ETag <.datastructures.ETag>` that will be added to the response. + exception_handlers: A mapping of status codes and/or exception types to handler functions. + guards: A sequence of :class:`Guard <.types.Guard>` callables. + media_type: A member of the :class:`MediaType <.enums.MediaType>` enum or a string with a + valid IANA Media-Type. + middleware: A sequence of :class:`Middleware <.types.Middleware>`. + name: A string identifying the route handler. + opt: A string keyed mapping of arbitrary values that can be accessed in :class:`Guards <.types.Guard>` or + wherever you have access to :class:`Request <.connection.Request>` or :class:`ASGI Scope <.types.Scope>`. + request_class: A custom subclass of :class:`Request <.connection.Request>` to be used as route handler's + default request. + response_class: A custom subclass of :class:`Response <.response.Response>` to be used as route handler's + default response. + response_cookies: A sequence of :class:`Cookie <.datastructures.Cookie>` instances. + response_headers: A string keyed mapping of :class:`ResponseHeader <.datastructures.ResponseHeader>` + instances. + responses: A mapping of additional status codes and a description of their expected content. + This information will be included in the OpenAPI schema + return_dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for serializing + outbound response data. + signature_namespace: A mapping of names to types for use in forward reference resolution during signature modelling. + status_code: An http status code for the response. Defaults to ``200`` for mixed method or ``GET``, ``PUT`` + and ``PATCH``, ``201`` for ``POST`` and ``204`` for ``DELETE``. + sync_to_thread: A boolean dictating whether the handler function will be executed in a worker thread or the + main event loop. This has an effect only for sync handler functions. See using sync handler functions. + content_encoding: A string describing the encoding of the content, e.g. ``base64``. + content_media_type: A string designating the media-type of the content, e.g. ``image/png``. + deprecated: A boolean dictating whether this route should be marked as deprecated in the OpenAPI schema. + description: Text used for the route's schema description section. + include_in_schema: A boolean flag dictating whether the route handler should be documented in the OpenAPI schema. + operation_class: :class:`Operation <.openapi.spec.operation.Operation>` to be used with the route's OpenAPI schema. + operation_id: Either a string or a callable returning a string. An identifier used for the route's schema operationId. + raises: A list of exception classes extending from litestar.HttpException that is used for the OpenAPI documentation. + This list should describe all exceptions raised within the route handler's function/method. The Litestar + ValidationException will be added automatically for the schema if any validation is involved. + response_description: Text used for the route's response schema description section. + security: A sequence of dictionaries that contain information about which security scheme can be used on the endpoint. + summary: Text used for the route's schema summary section. + tags: A sequence of string tags that will be appended to the OpenAPI schema. + type_decoders: A sequence of tuples, each composed of a predicate testing for type identity and a msgspec + hook for deserialization. + type_encoders: A mapping of types to callables that transform them into types supported for serialization. + handler_class: Route handler class instantiated by the decorator + **kwargs: Any additional kwarg - will be set in the opt dictionary. + """ + + def decorator(fn: AnyCallable) -> HTTPRouteHandler: + return handler_class( + fn=fn, + after_request=after_request, + after_response=after_response, + background=background, + before_request=before_request, + cache=cache, + cache_control=cache_control, + cache_key_builder=cache_key_builder, + content_encoding=content_encoding, + content_media_type=content_media_type, + dependencies=dependencies, + deprecated=deprecated, + description=description, + dto=dto, + etag=etag, + exception_handlers=exception_handlers, + guards=guards, + http_method=HttpMethod.DELETE, + include_in_schema=include_in_schema, + media_type=media_type, + middleware=middleware, + name=name, + operation_class=operation_class, + operation_id=operation_id, + opt=opt, + path=path, + raises=raises, + request_class=request_class, + response_class=response_class, + response_cookies=response_cookies, + response_description=response_description, + response_headers=response_headers, + responses=responses, + return_dto=return_dto, + security=security, + signature_namespace=signature_namespace, + status_code=status_code, + summary=summary, + sync_to_thread=sync_to_thread, + tags=tags, + type_decoders=type_decoders, + type_encoders=type_encoders, + **kwargs, + ) + + return decorator diff --git a/litestar/handlers/websocket_handlers/listener.py b/litestar/handlers/websocket_handlers/listener.py index 8e702ea1aa..aba965fc8d 100644 --- a/litestar/handlers/websocket_handlers/listener.py +++ b/litestar/handlers/websocket_handlers/listener.py @@ -42,8 +42,6 @@ if TYPE_CHECKING: from typing import Coroutine - from typing_extensions import Self - from litestar import Router from litestar.dto import AbstractDTO from litestar.types.asgi_types import WebSocketMode @@ -74,6 +72,7 @@ def __init__( self, path: str | list[str] | None = None, *, + fn: AnyCallable, connection_lifespan: Callable[..., AbstractAsyncContextManager[Any]] | None = None, dependencies: Dependencies | None = None, dto: type[AbstractDTO] | None | EmptyType = Empty, @@ -97,6 +96,7 @@ def __init__( self, path: str | list[str] | None = None, *, + fn: AnyCallable, connection_accept_handler: Callable[[WebSocket], Coroutine[Any, Any, None]] = WebSocket.accept, dependencies: Dependencies | None = None, dto: type[AbstractDTO] | None | EmptyType = Empty, @@ -121,6 +121,7 @@ def __init__( self, path: str | list[str] | None = None, *, + fn: AnyCallable, connection_accept_handler: Callable[[WebSocket], Coroutine[Any, Any, None]] = WebSocket.accept, connection_lifespan: Callable[..., AbstractAsyncContextManager[Any]] | None = None, dependencies: Dependencies | None = None, @@ -146,6 +147,7 @@ def __init__( Args: path: A path fragment for the route handler function or a sequence of path fragments. If not given defaults to ``/`` + fn: The handler function connection_accept_handler: A callable that accepts a :class:`WebSocket <.connection.WebSocket>` instance and returns a coroutine that when awaited, will accept the connection. Defaults to ``WebSocket.accept``. connection_lifespan: An asynchronous context manager, handling the lifespan of the connection. By default, @@ -210,6 +212,7 @@ def __init__( listener_dependencies["on_disconnect_dependencies"] = create_stub_dependency(self.on_disconnect) super().__init__( + fn=fn, path=path, dependencies=listener_dependencies, exception_handlers=exception_handlers, @@ -226,7 +229,7 @@ def __init__( **kwargs, ) - def __call__(self, fn: AnyCallable) -> Self: + def _prepare_fn(self, fn: AnyCallable) -> ListenerHandler: parsed_signature = ParsedSignature.from_fn(fn, self.resolve_signature_namespace()) if "data" not in parsed_signature.parameters: @@ -248,10 +251,8 @@ def __call__(self, fn: AnyCallable) -> Self: }, ) - return super().__call__( - ListenerHandler( - listener=self, fn=fn, parsed_signature=parsed_signature, namespace=self.resolve_signature_namespace() - ) + return ListenerHandler( + listener=self, fn=fn, parsed_signature=parsed_signature, namespace=self.resolve_signature_namespace() ) def _validate_handler_function(self) -> None: @@ -319,9 +320,6 @@ def resolve_send_handler(self) -> Callable[[WebSocket, Any], Coroutine[None, Non return self._send_handler -websocket_listener = WebsocketListenerRouteHandler - - class WebsocketListener(ABC): path: str | list[str] | None = None """A path fragment for the route handler function or a sequence of path fragments. If not given defaults to ``/``""" @@ -398,7 +396,8 @@ def to_handler(self) -> WebsocketListenerRouteHandler: type_decoders=self.type_decoders, type_encoders=self.type_encoders, websocket_class=self.websocket_class, - )(self.on_receive) + fn=self.on_receive, + ) handler.owner = self._owner return handler @@ -414,3 +413,140 @@ def on_receive(self, *args: Any, **kwargs: Any) -> Any: according to handler configuration. """ raise NotImplementedError + + +@overload +def websocket_listener( + path: str | list[str] | None = None, + *, + connection_lifespan: Callable[..., AbstractAsyncContextManager[Any]] | None = None, + dependencies: Dependencies | None = None, + dto: type[AbstractDTO] | None | EmptyType = Empty, + exception_handlers: dict[int | type[Exception], ExceptionHandler] | None = None, + guards: list[Guard] | None = None, + middleware: list[Middleware] | None = None, + receive_mode: WebSocketMode = "text", + send_mode: WebSocketMode = "text", + name: str | None = None, + opt: dict[str, Any] | None = None, + return_dto: type[AbstractDTO] | None | EmptyType = Empty, + signature_namespace: Mapping[str, Any] | None = None, + type_decoders: TypeDecodersSequence | None = None, + type_encoders: TypeEncodersMap | None = None, + websocket_class: type[WebSocket] | None = None, + **kwargs: Any, +) -> Callable[[AnyCallable], WebsocketListenerRouteHandler]: ... + + +@overload +def websocket_listener( + path: str | list[str] | None = None, + *, + connection_accept_handler: Callable[[WebSocket], Coroutine[Any, Any, None]] = WebSocket.accept, + dependencies: Dependencies | None = None, + dto: type[AbstractDTO] | None | EmptyType = Empty, + exception_handlers: dict[int | type[Exception], ExceptionHandler] | None = None, + guards: list[Guard] | None = None, + middleware: list[Middleware] | None = None, + receive_mode: WebSocketMode = "text", + send_mode: WebSocketMode = "text", + name: str | None = None, + on_accept: AnyCallable | None = None, + on_disconnect: AnyCallable | None = None, + opt: dict[str, Any] | None = None, + return_dto: type[AbstractDTO] | None | EmptyType = Empty, + signature_namespace: Mapping[str, Any] | None = None, + type_decoders: TypeDecodersSequence | None = None, + type_encoders: TypeEncodersMap | None = None, + websocket_class: type[WebSocket] | None = None, + **kwargs: Any, +) -> Callable[[AnyCallable], WebsocketListenerRouteHandler]: ... + + +def websocket_listener( + path: str | list[str] | None = None, + *, + connection_accept_handler: Callable[[WebSocket], Coroutine[Any, Any, None]] = WebSocket.accept, + connection_lifespan: Callable[..., AbstractAsyncContextManager[Any]] | None = None, + dependencies: Dependencies | None = None, + dto: type[AbstractDTO] | None | EmptyType = Empty, + exception_handlers: dict[int | type[Exception], ExceptionHandler] | None = None, + guards: list[Guard] | None = None, + middleware: list[Middleware] | None = None, + receive_mode: WebSocketMode = "text", + send_mode: WebSocketMode = "text", + name: str | None = None, + on_accept: AnyCallable | None = None, + on_disconnect: AnyCallable | None = None, + opt: dict[str, Any] | None = None, + return_dto: type[AbstractDTO] | None | EmptyType = Empty, + signature_namespace: Mapping[str, Any] | None = None, + type_decoders: TypeDecodersSequence | None = None, + type_encoders: TypeEncodersMap | None = None, + websocket_class: type[WebSocket] | None = None, + **kwargs: Any, +) -> Callable[[AnyCallable], WebsocketListenerRouteHandler]: + """Create a :class:`WebsocketListenerRouteHandler`. + + Args: + path: A path fragment for the route handler function or a sequence of path fragments. If not given defaults + to ``/`` + connection_accept_handler: A callable that accepts a :class:`WebSocket <.connection.WebSocket>` instance + and returns a coroutine that when awaited, will accept the connection. Defaults to ``WebSocket.accept``. + connection_lifespan: An asynchronous context manager, handling the lifespan of the connection. By default, + it calls the ``connection_accept_handler``, ``on_connect`` and ``on_disconnect``. Can request any + dependencies, for example the :class:`WebSocket <.connection.WebSocket>` connection + dependencies: A string keyed mapping of dependency :class:`Provider <.di.Provide>` instances. + dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for (de)serializing and + validation of request data. + exception_handlers: A mapping of status codes and/or exception types to handler functions. + guards: A sequence of :class:`Guard <.types.Guard>` callables. + middleware: A sequence of :class:`Middleware <.types.Middleware>`. + receive_mode: Websocket mode to receive data in, either `text` or `binary`. + send_mode: Websocket mode to receive data in, either `text` or `binary`. + name: A string identifying the route handler. + on_accept: Callback invoked after a connection has been accepted. Can request any dependencies, for example + the :class:`WebSocket <.connection.WebSocket>` connection + on_disconnect: Callback invoked after a connection has been closed. Can request any dependencies, for + example the :class:`WebSocket <.connection.WebSocket>` connection + opt: A string keyed mapping of arbitrary values that can be accessed in :class:`Guards <.types.Guard>` or + wherever you have access to :class:`Request <.connection.Request>` or + :class:`ASGI Scope <.types.Scope>`. + return_dto: :class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for serializing + outbound response data. + signature_namespace: A mapping of names to types for use in forward reference resolution during signature + modelling. + type_decoders: A sequence of tuples, each composed of a predicate testing for type identity and a msgspec + hook for deserialization. + type_encoders: A mapping of types to callables that transform them into types supported for serialization. + **kwargs: Any additional kwarg - will be set in the opt dictionary. + websocket_class: A custom subclass of :class:`WebSocket <.connection.WebSocket>` to be used as route handler's + default websocket class. + """ + + def decorator(fn: AnyCallable) -> WebsocketListenerRouteHandler: + return WebsocketListenerRouteHandler( + fn=fn, + path=path, + connection_accept_handler=connection_accept_handler, + connection_lifespan=connection_lifespan, + dependencies=dependencies, + dto=dto, + exception_handlers=exception_handlers, + guard=guards, + middleware=middleware, + receive_mode=receive_mode, + send_mode=send_mode, + name=name, + on_accept=on_accept, + on_disconnect=on_disconnect, + opt=opt, + return_dto=return_dto, + signature_namespace=signature_namespace, + type_decoders=type_decoders, + type_encoders=type_encoders, + websocket_class=websocket_class, + **kwargs, + ) + + return decorator diff --git a/litestar/handlers/websocket_handlers/route_handler.py b/litestar/handlers/websocket_handlers/route_handler.py index 2dd3a726ac..8ab76550f6 100644 --- a/litestar/handlers/websocket_handlers/route_handler.py +++ b/litestar/handlers/websocket_handlers/route_handler.py @@ -1,11 +1,11 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Mapping +from typing import TYPE_CHECKING, Any, Callable, Mapping from litestar.connection import WebSocket from litestar.exceptions import ImproperlyConfiguredException from litestar.handlers import BaseRouteHandler -from litestar.types import Empty +from litestar.types import AsyncAnyCallable, Empty from litestar.types.builtin_types import NoneType from litestar.utils.predicates import is_async_callable @@ -18,17 +18,13 @@ class WebsocketRouteHandler(BaseRouteHandler): - """Websocket route handler decorator. - - Use this decorator to decorate websocket handler functions. - """ - __slots__ = ("websocket_class", "_kwargs_model") def __init__( self, path: str | list[str] | None = None, *, + fn: AsyncAnyCallable, dependencies: Dependencies | None = None, exception_handlers: dict[int | type[Exception], ExceptionHandler] | None = None, guards: list[Guard] | None = None, @@ -39,11 +35,14 @@ def __init__( websocket_class: type[WebSocket] | None = None, **kwargs: Any, ) -> None: - """Initialize ``WebsocketRouteHandler`` + """Route handler for WebSocket routes. Args: path: A path fragment for the route handler function or a sequence of path fragments. If not given defaults to ``/`` + fn: The handler function + + .. versionadded:: 3.0 dependencies: A string keyed mapping of dependency :class:`Provider <.di.Provide>` instances. exception_handlers: A mapping of status codes and/or exception types to handler functions. guards: A sequence of :class:`Guard <.types.Guard>` callables. @@ -53,7 +52,6 @@ def __init__( wherever you have access to :class:`Request <.connection.Request>` or :class:`ASGI Scope <.types.Scope>`. signature_namespace: A mapping of names to types for use in forward reference resolution during signature modelling. - type_encoders: A mapping of types to callables that transform them into types supported for serialization. **kwargs: Any additional kwarg - will be set in the opt dictionary. websocket_class: A custom subclass of :class:`WebSocket <.connection.WebSocket>` to be used as route handler's default websocket class. @@ -62,6 +60,7 @@ def __init__( self._kwargs_model: KwargsModel | EmptyType = Empty super().__init__( + fn=fn, path=path, dependencies=dependencies, exception_handlers=exception_handlers, @@ -145,4 +144,53 @@ async def handle(self, connection: WebSocket[Any, Any, Any]) -> None: await self.fn(**parsed_kwargs) -websocket = WebsocketRouteHandler +def websocket( + path: str | list[str] | None = None, + *, + dependencies: Dependencies | None = None, + exception_handlers: dict[int | type[Exception], ExceptionHandler] | None = None, + guards: list[Guard] | None = None, + middleware: list[Middleware] | None = None, + name: str | None = None, + opt: dict[str, Any] | None = None, + signature_namespace: Mapping[str, Any] | None = None, + websocket_class: type[WebSocket] | None = None, + handler_class: type[WebsocketRouteHandler] = WebsocketRouteHandler, + **kwargs: Any, +) -> Callable[[AsyncAnyCallable], WebsocketRouteHandler]: + """Create a :class:`WebsocketRouteHandler`. + + Args: + path: A path fragment for the route handler function or a sequence of path fragments. If not given defaults + to ``/`` + dependencies: A string keyed mapping of dependency :class:`Provider <.di.Provide>` instances. + exception_handlers: A mapping of status codes and/or exception types to handler functions. + guards: A sequence of :class:`Guard <.types.Guard>` callables. + middleware: A sequence of :class:`Middleware <.types.Middleware>`. + name: A string identifying the route handler. + opt: A string keyed mapping of arbitrary values that can be accessed in :class:`Guards <.types.Guard>` or + wherever you have access to :class:`Request <.connection.Request>` or + :class:`ASGI Scope <.types.Scope>`. + signature_namespace: A mapping of names to types for use in forward reference resolution during signature modelling. + websocket_class: A custom subclass of :class:`WebSocket <.connection.WebSocket>` to be used as route handler's + default websocket class. + handler_class: Route handler class instantiated by the decorator + **kwargs: Any additional kwarg - will be set in the opt dictionary. + """ + + def decorator(fn: AsyncAnyCallable) -> WebsocketRouteHandler: + return handler_class( + path=path, + fn=fn, + dependencies=dependencies, + exception_handlers=exception_handlers, + guards=guards, + middleware=middleware, + name=name, + opt=opt, + signature_namespace=signature_namespace, + websocket_class=websocket_class, + **kwargs, + ) + + return decorator diff --git a/litestar/logging/config.py b/litestar/logging/config.py index c084c5d034..2b1587c89f 100644 --- a/litestar/logging/config.py +++ b/litestar/logging/config.py @@ -10,7 +10,7 @@ from litestar.exceptions import ImproperlyConfiguredException, MissingDependencyException from litestar.serialization.msgspec_hooks import _msgspec_json_encoder from litestar.utils.dataclass import simple_asdict -from litestar.utils.deprecation import deprecated +from litestar.utils.deprecation import deprecated, warn_deprecation __all__ = ("BaseLoggingConfig", "LoggingConfig", "StructLoggingConfig") @@ -90,34 +90,45 @@ def _get_default_handlers() -> dict[str, dict[str, Any]]: def _default_exception_logging_handler_factory( - is_struct_logger: bool, traceback_line_limit: int + is_struct_logger: bool, + traceback_line_limit: int, ) -> ExceptionLoggingHandler: """Create an exception logging handler function. Args: is_struct_logger: Whether the logger is a structlog instance. traceback_line_limit: Maximal number of lines to log from the - traceback. + traceback. This parameter is deprecated and ignored. Returns: An exception logging handler. """ - def _default_exception_logging_handler(logger: Logger, scope: Scope, tb: list[str]) -> None: - # we limit the length of the stack trace to 20 lines. - first_line = tb.pop(0) + if traceback_line_limit != -1: + warn_deprecation( + version="2.9.0", + deprecated_name="traceback_line_limit", + kind="parameter", + info="The value is ignored. Use a custom 'exception_logging_handler' instead.", + removal_in="3.0", + ) + + if is_struct_logger: - if is_struct_logger: + def _default_exception_logging_handler(logger: Logger, scope: Scope, tb: list[str]) -> None: logger.exception( - "Uncaught Exception", + "Uncaught exception", connection_type=scope["type"], path=scope["path"], - traceback="".join(tb[-traceback_line_limit:]), ) - else: - stack_trace = first_line + "".join(tb[-traceback_line_limit:]) + + else: + + def _default_exception_logging_handler(logger: Logger, scope: Scope, tb: list[str]) -> None: logger.exception( - "exception raised on %s connection to route %s\n\n%s", scope["type"], scope["path"], stack_trace + "Uncaught exception (connection_type=%s, path=%s):", + scope["type"], + scope["path"], ) return _default_exception_logging_handler @@ -131,7 +142,11 @@ class BaseLoggingConfig(ABC): log_exceptions: Literal["always", "debug", "never"] """Should exceptions be logged, defaults to log exceptions when ``app.debug == True``'""" traceback_line_limit: int - """Max number of lines to print for exception traceback""" + """Max number of lines to print for exception traceback. + + .. deprecated:: 2.9.0 + This parameter is deprecated and ignored. It will be removed in a future release. + """ exception_logging_handler: ExceptionLoggingHandler | None """Handler function for logging exceptions.""" @@ -205,8 +220,12 @@ class LoggingConfig(BaseLoggingConfig): """Should the root logger be configured, defaults to True for ease of configuration.""" log_exceptions: Literal["always", "debug", "never"] = field(default="debug") """Should exceptions be logged, defaults to log exceptions when 'app.debug == True'""" - traceback_line_limit: int = field(default=20) - """Max number of lines to print for exception traceback""" + traceback_line_limit: int = field(default=-1) + """Max number of lines to print for exception traceback. + + .. deprecated:: 2.9.0 + This parameter is deprecated and ignored. It will be removed in a future release. + """ exception_logging_handler: ExceptionLoggingHandler | None = field(default=None) """Handler function for logging exceptions.""" @@ -421,8 +440,12 @@ class StructLoggingConfig(BaseLoggingConfig): """Whether to cache the logger configuration and reuse.""" log_exceptions: Literal["always", "debug", "never"] = field(default="debug") """Should exceptions be logged, defaults to log exceptions when 'app.debug == True'""" - traceback_line_limit: int = field(default=20) - """Max number of lines to print for exception traceback""" + traceback_line_limit: int = field(default=-1) + """Max number of lines to print for exception traceback. + + .. deprecated:: 2.9.0 + This parameter is deprecated and ignored. It will be removed in a future release. + """ exception_logging_handler: ExceptionLoggingHandler | None = field(default=None) """Handler function for logging exceptions.""" pretty_print_tty: bool = field(default=True) @@ -430,9 +453,9 @@ class StructLoggingConfig(BaseLoggingConfig): def __post_init__(self) -> None: if self.processors is None: - self.processors = default_structlog_processors(not sys.stderr.isatty() and self.pretty_print_tty) + self.processors = default_structlog_processors(as_json=self.as_json()) if self.logger_factory is None: - self.logger_factory = default_logger_factory(not sys.stderr.isatty() and self.pretty_print_tty) + self.logger_factory = default_logger_factory(as_json=self.as_json()) if self.log_exceptions != "never" and self.exception_logging_handler is None: self.exception_logging_handler = _default_exception_logging_handler_factory( is_struct_logger=True, traceback_line_limit=self.traceback_line_limit @@ -445,15 +468,16 @@ def __post_init__(self) -> None: formatters={ "standard": { "()": structlog.stdlib.ProcessorFormatter, - "processors": default_structlog_standard_lib_processors( - as_json=not sys.stderr.isatty() and self.pretty_print_tty - ), + "processors": default_structlog_standard_lib_processors(as_json=self.as_json()), } } ) except ImportError: self.standard_lib_logging_config = LoggingConfig() + def as_json(self) -> bool: + return not (sys.stderr.isatty() and self.pretty_print_tty) + def configure(self) -> GetLogger: """Return logger with the given configuration. diff --git a/litestar/middleware/_internal/exceptions/middleware.py b/litestar/middleware/_internal/exceptions/middleware.py index 9006b29669..69cf74097d 100644 --- a/litestar/middleware/_internal/exceptions/middleware.py +++ b/litestar/middleware/_internal/exceptions/middleware.py @@ -28,6 +28,7 @@ ExceptionHandler, ExceptionHandlersMap, Logger, + Message, Receive, Scope, Send, @@ -113,9 +114,19 @@ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: Returns: None """ + scope_state = ScopeState.from_scope(scope) + + async def capture_response_started(event: Message) -> None: + if event["type"] == "http.response.start": + scope_state.response_started = True + await send(event) + try: - await self.app(scope, receive, send) - except Exception as e: # noqa: BLE001 + await self.app(scope, receive, capture_response_started) + except Exception as e: + if scope_state.response_started: + raise LitestarException("Exception caught after response started") from e + litestar_app = scope["app"] if litestar_app.logging_config and (logger := litestar_app.logger): diff --git a/litestar/middleware/logging.py b/litestar/middleware/logging.py index c986eb6433..1f73c7079c 100644 --- a/litestar/middleware/logging.py +++ b/litestar/middleware/logging.py @@ -191,7 +191,9 @@ def extract_response_data(self, scope: Scope) -> dict[str, Any]: connection_state = ScopeState.from_scope(scope) extracted_data = self.response_extractor( messages=( - connection_state.log_context.pop(HTTP_RESPONSE_START), + # NOTE: we don't pop the start message from the logging context in case + # there are multiple body messages to be logged + connection_state.log_context[HTTP_RESPONSE_START], connection_state.log_context.pop(HTTP_RESPONSE_BODY), ), ) @@ -224,6 +226,10 @@ async def send_wrapper(message: Message) -> None: elif message["type"] == HTTP_RESPONSE_BODY: connection_state.log_context[HTTP_RESPONSE_BODY] = message self.log_response(scope=scope) + + if not message["more_body"]: + connection_state.log_context.clear() + await send(message) return send_wrapper diff --git a/litestar/pagination.py b/litestar/pagination.py index 294a13aa7b..6e81371958 100644 --- a/litestar/pagination.py +++ b/litestar/pagination.py @@ -39,23 +39,31 @@ class ClassicPagination(Generic[T]): """Total number of pages.""" -@dataclass -class OffsetPagination(Generic[T]): - """Container for data returned using limit/offset pagination.""" - - __slots__ = ("items", "limit", "offset", "total") - - items: List[T] - """List of data being sent as part of the response.""" - limit: int - """Maximal number of items to send.""" - offset: int - """Offset from the beginning of the query. - - Identical to an index. - """ - total: int - """Total number of items.""" +# AA requires it's own `OffsetPagination` class in versions greater that 0.9.0 +# If we find it, use it. +try: + from advanced_alchemy.service import ( + OffsetPagination, # pyright: ignore[reportMissingImports,reportGeneralTypeIssues] + ) +except ImportError: + + @dataclass + class OffsetPagination(Generic[T]): # type: ignore[no-redef] + """Container for data returned using limit/offset pagination.""" + + __slots__ = ("items", "limit", "offset", "total") + + items: List[T] + """List of data being sent as part of the response.""" + limit: int + """Maximal number of items to send.""" + offset: int + """Offset from the beginning of the query. + + Identical to an index. + """ + total: int + """Total number of items.""" @dataclass diff --git a/litestar/plugins/base.py b/litestar/plugins/base.py index 65710c9fc0..41b0c34bb5 100644 --- a/litestar/plugins/base.py +++ b/litestar/plugins/base.py @@ -121,9 +121,6 @@ class CLIPlugin(CLIPluginProtocol): __slots__ = () - def on_cli_init(self, cli: Group) -> None: - return super().on_cli_init(cli) - @contextmanager def server_lifespan(self, app: Litestar) -> Iterator[None]: yield diff --git a/litestar/plugins/flash.py b/litestar/plugins/flash.py index 3b71987574..b7d67f7eb1 100644 --- a/litestar/plugins/flash.py +++ b/litestar/plugins/flash.py @@ -6,16 +6,21 @@ from dataclasses import dataclass from typing import TYPE_CHECKING, Any, Mapping +import litestar.exceptions +from litestar import Request from litestar.exceptions import MissingDependencyException +from litestar.middleware import DefineMiddleware +from litestar.middleware.session import SessionMiddleware from litestar.plugins import InitPluginProtocol +from litestar.security.session_auth.middleware import MiddlewareWrapper from litestar.template.base import _get_request_from_context -from litestar.utils.scope.state import ScopeState +from litestar.utils.predicates import is_class_and_subclass if TYPE_CHECKING: from collections.abc import Callable from litestar.config.app import AppConfig - from litestar.connection import ASGIConnection + from litestar.connection.base import AuthT, StateT, UserT from litestar.template import TemplateConfig @@ -46,6 +51,13 @@ def on_app_init(self, app_config: AppConfig) -> AppConfig: Returns: The application configuration with the message callable registered. """ + for mw in app_config.middleware: + if isinstance(mw, DefineMiddleware) and is_class_and_subclass( + mw.middleware, (MiddlewareWrapper, SessionMiddleware) + ): + break + else: + raise litestar.exceptions.ImproperlyConfiguredException("Flash messages require a session middleware.") template_callable: Callable[[Any], Any] = get_flashes with suppress(MissingDependencyException): from litestar.contrib.minijinja import MiniJinjaTemplateEngine, _transform_state @@ -57,26 +69,13 @@ def on_app_init(self, app_config: AppConfig) -> AppConfig: return app_config -def flash(connection: ASGIConnection, message: str, category: str) -> None: - """Add a flash message to the request scope. - - Args: - connection: The connection instance. - message: The message to flash. - category: The category of the message. - """ - scope_state = ScopeState.from_scope(connection.scope) - scope_state.flash_messages.append({"message": message, "category": category}) +def flash( + request: Request[UserT, AuthT, StateT], + message: Any, + category: str, +) -> None: + request.session.setdefault("_messages", []).append({"message": message, "category": category}) def get_flashes(context: Mapping[str, Any]) -> Any: - """Get flash messages from the request scope, if any. - - Args: - context: The context dictionary. - - Returns: - The flash messages, if any. - """ - scope_state = ScopeState.from_scope(_get_request_from_context(context).scope) - return scope_state.flash_messages + return _get_request_from_context(context).session.pop("_messages", []) diff --git a/litestar/plugins/sqlalchemy.py b/litestar/plugins/sqlalchemy.py index d65d7126d4..7c2f425497 100644 --- a/litestar/plugins/sqlalchemy.py +++ b/litestar/plugins/sqlalchemy.py @@ -1,54 +1,31 @@ -from advanced_alchemy import filters, types -from advanced_alchemy.base import ( - AuditColumns, - BigIntAuditBase, - BigIntBase, - BigIntPrimaryKey, - CommonTableAttributes, - UUIDAuditBase, - UUIDBase, - UUIDPrimaryKey, - orm_registry, -) -from advanced_alchemy.extensions.litestar import ( - AlembicAsyncConfig, - AlembicCommands, - AlembicSyncConfig, - AsyncSessionConfig, - EngineConfig, - SQLAlchemyAsyncConfig, - SQLAlchemyDTO, - SQLAlchemyDTOConfig, - SQLAlchemyInitPlugin, - SQLAlchemyPlugin, - SQLAlchemySerializationPlugin, - SQLAlchemySyncConfig, - SyncSessionConfig, -) +from litestar.utils import warn_deprecation -__all__ = ( - "orm_registry", - "filters", - "types", - "AuditColumns", - "BigIntAuditBase", - "BigIntBase", - "UUIDAuditBase", - "UUIDPrimaryKey", - "CommonTableAttributes", - "UUIDBase", - "BigIntPrimaryKey", - "AlembicCommands", - "AlembicAsyncConfig", - "AlembicSyncConfig", - "AsyncSessionConfig", - "SyncSessionConfig", - "SQLAlchemyDTO", - "SQLAlchemyDTOConfig", - "SQLAlchemyAsyncConfig", - "SQLAlchemyInitPlugin", - "SQLAlchemyPlugin", - "SQLAlchemySerializationPlugin", - "SQLAlchemySyncConfig", - "EngineConfig", -) + +def __getattr__(attr_name: str) -> object: + from advanced_alchemy.extensions import litestar as aa # pyright: ignore[reportMissingImports] + + if attr_name in { + "AuditColumns", + "BigIntAuditBase", + "BigIntBase", + "BigIntPrimaryKey", + "CommonTableAttributes", + "UUIDAuditBase", + "UUIDBase", + "UUIDPrimaryKey", + "orm_registry", + }: + warn_deprecation( + deprecated_name=f"litestar.plugins.sqlalchemy.{attr_name}", + version="2.9.0", + kind="import", + removal_in="3.0", + info=f"importing {attr_name} from 'litestar.plugins.sqlalchemy' is deprecated, please" + f"import it from 'litestar.plugins.sqlalchemy.base.{attr_name}' instead", + ) + value = globals()[attr_name] = getattr(aa.base, attr_name) + return value + if attr_name in aa.__all__: + value = globals()[attr_name] = getattr(aa, attr_name) + return value + raise AttributeError(f"module {__name__!r} has no attribute {attr_name!r}") diff --git a/litestar/router.py b/litestar/router.py index 07b6b73e86..94cbbe58f8 100644 --- a/litestar/router.py +++ b/litestar/router.py @@ -279,41 +279,25 @@ def route_handler_method_map(self) -> dict[str, RouteHandlerMapItem]: @classmethod def get_route_handler_map( cls, - value: Controller | RouteHandlerType | Router, + value: RouteHandlerType | Router, ) -> dict[str, RouteHandlerMapItem]: """Map route handlers to HTTP methods.""" if isinstance(value, Router): return value.route_handler_method_map - if isinstance(value, (HTTPRouteHandler, ASGIRouteHandler, WebsocketRouteHandler)): - copied_value = copy(value) - if isinstance(value, HTTPRouteHandler): - return {path: {http_method: copied_value for http_method in value.http_methods} for path in value.paths} - - return { - path: {"websocket" if isinstance(value, WebsocketRouteHandler) else "asgi": copied_value} - for path in value.paths - } - - # handle controllers - handlers_map: defaultdict[str, RouteHandlerMapItem] = defaultdict(dict) - for route_handler in value.get_route_handlers(): - for handler_path in route_handler.paths: - path = join_paths([value.path, handler_path]) if handler_path else value.path - if isinstance(route_handler, HTTPRouteHandler): - for http_method in route_handler.http_methods: - handlers_map[path][http_method] = route_handler - else: - handlers_map[path]["websocket" if isinstance(route_handler, WebsocketRouteHandler) else "asgi"] = ( - cast("WebsocketRouteHandler | ASGIRouteHandler", route_handler) - ) + copied_value = copy(value) + if isinstance(value, HTTPRouteHandler): + return {path: {http_method: copied_value for http_method in value.http_methods} for path in value.paths} - return handlers_map + return { + path: {"websocket" if isinstance(value, WebsocketRouteHandler) else "asgi": copied_value} + for path in value.paths + } - def _validate_registration_value(self, value: ControllerRouterHandler) -> Controller | RouteHandlerType | Router: + def _validate_registration_value(self, value: ControllerRouterHandler) -> RouteHandlerType | Router: """Ensure values passed to the register method are supported.""" if is_class_and_subclass(value, Controller): - return value(owner=self) + return value(owner=self).as_router() # this narrows down to an ABC, but we assume a non-abstract subclass of the ABC superclass if is_class_and_subclass(value, WebsocketListener): diff --git a/litestar/serialization/msgspec_hooks.py b/litestar/serialization/msgspec_hooks.py index ba98ee4a4f..06c6a65324 100644 --- a/litestar/serialization/msgspec_hooks.py +++ b/litestar/serialization/msgspec_hooks.py @@ -22,6 +22,7 @@ from litestar.datastructures.secret_values import SecretBytes, SecretString from litestar.exceptions import SerializationException from litestar.types import Empty, EmptyType, Serializer, TypeDecodersSequence +from litestar.utils.typing import get_origin_or_inner_type if TYPE_CHECKING: from litestar.types import TypeEncodersMap @@ -107,8 +108,19 @@ def default_deserializer( from litestar.datastructures.state import ImmutableState - if isinstance(value, target_type): - return value + try: + if isinstance(value, target_type): + return value + except TypeError as exc: + # we might get a TypeError here if target_type is a subscribed generic. For + # performance reasons, we let this happen and only unwrap this when we're + # certain this might be the case + if (origin := get_origin_or_inner_type(target_type)) is not None: + target_type = origin + if isinstance(value, target_type): + return value + else: + raise exc if type_decoders: for predicate, decoder in type_decoders: diff --git a/litestar/static_files.py b/litestar/static_files.py index 507c691f0a..d8ee22c0ac 100644 --- a/litestar/static_files.py +++ b/litestar/static_files.py @@ -1,5 +1,6 @@ from __future__ import annotations +import os from os.path import commonpath from pathlib import Path, PurePath from typing import TYPE_CHECKING, Any, Literal, Mapping, Sequence @@ -83,7 +84,7 @@ def create_static_files_router( if file_system is None: file_system = BaseLocalFileSystem() - directories = list(directories) + directories = tuple(os.path.normpath(Path(p).resolve() if resolve_symlinks else Path(p)) for p in directories) _validate_config(path=path, directories=directories, file_system=file_system) path = normalize_path(path) @@ -225,19 +226,26 @@ async def _get_fs_info( try: joined_path = Path(directory, file_path) file_info = await adapter.info(joined_path) - if file_info and commonpath([str(directory), file_info["name"], joined_path]) == str(directory): + normalized_file_path = os.path.normpath(joined_path) + directory_path = str(directory) + if ( + file_info + and commonpath([directory_path, file_info["name"], joined_path]) == directory_path + and os.path.commonpath([directory, normalized_file_path]) == directory_path + and (file_info := await adapter.info(joined_path)) + ): return joined_path, file_info except FileNotFoundError: continue return None, None -def _validate_config(path: str, directories: list[PathType], file_system: Any) -> None: +def _validate_config(path: str, directories: tuple[PathType, ...], file_system: Any) -> None: if not path: - raise ImproperlyConfiguredException("path must be a non-zero length string,") + raise ImproperlyConfiguredException("path must be a non-zero length string") if not directories or not any(bool(d) for d in directories): - raise ImproperlyConfiguredException("directories must include at least one path.") + raise ImproperlyConfiguredException("directories must include at least one path") if "{" in path: raise ImproperlyConfiguredException("path parameters are not supported for static files") diff --git a/litestar/static_files/base.py b/litestar/static_files/base.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/litestar/static_files/config.py b/litestar/static_files/config.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/litestar/stores/file.py b/litestar/stores/file.py index 25c52eb6b5..0320e47464 100644 --- a/litestar/stores/file.py +++ b/litestar/stores/file.py @@ -28,15 +28,25 @@ def _safe_file_name(name: str) -> str: class FileStore(NamespacedStore): """File based, thread and process safe, asynchronous key/value store.""" - __slots__ = {"path": "file path"} + __slots__ = {"path": "file path", "create_directories": "flag to create directories in path"} - def __init__(self, path: PathLike[str]) -> None: + def __init__(self, path: PathLike[str], *, create_directories: bool = False) -> None: """Initialize ``FileStorage``. Args: path: Path to store data under + create_directories: Create the directories in ``path`` if they don't exist + Default: ``False`` + + .. versionadded:: 2.9.0 """ self.path = Path(path) + self.create_directories = create_directories + + async def __aenter__(self) -> None: + if self.create_directories: + await self.path.mkdir(exist_ok=True, parents=True) + return def with_namespace(self, namespace: str) -> FileStore: """Return a new instance of :class:`FileStore`, using a sub-path of the current store's path.""" diff --git a/litestar/testing/client/async_client.py b/litestar/testing/client/async_client.py index 0e4d779170..4e28bef4ac 100644 --- a/litestar/testing/client/async_client.py +++ b/litestar/testing/client/async_client.py @@ -2,11 +2,9 @@ from contextlib import AsyncExitStack from typing import TYPE_CHECKING, Any, Generic, Mapping, Sequence, TypeVar -from urllib.parse import urljoin -from httpx import USE_CLIENT_DEFAULT, AsyncClient, Response +from httpx import USE_CLIENT_DEFAULT, AsyncClient -from litestar import HttpMethod from litestar.testing.client.base import BaseTestClient from litestar.testing.life_span_handler import LifeSpanHandler from litestar.testing.transport import ConnectionUpgradeExceptionError, TestClientTransport @@ -19,11 +17,7 @@ CookieTypes, HeaderTypes, QueryParamTypes, - RequestContent, - RequestData, - RequestFiles, TimeoutTypes, - URLTypes, ) from typing_extensions import Self @@ -107,369 +101,6 @@ def wait_shutdown() -> None: async def __aexit__(self, *args: Any) -> None: await self.exit_stack.aclose() - async def request( - self, - method: str, - url: URLTypes, - *, - content: RequestContent | None = None, - data: RequestData | None = None, - files: RequestFiles | None = None, - json: Any | None = None, - params: QueryParamTypes | None = None, - headers: HeaderTypes | None = None, - cookies: CookieTypes | None = None, - auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT, - follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, - timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, - extensions: Mapping[str, Any] | None = None, - ) -> Response: - """Sends a request. - - Args: - method: An HTTP method. - url: URL or path for the request. - content: Request content. - data: Form encoded data. - files: Multipart files to send. - json: JSON data to send. - params: Query parameters. - headers: Request headers. - cookies: Request cookies. - auth: Auth headers. - follow_redirects: Whether to follow redirects. - timeout: Request timeout. - extensions: Dictionary of ASGI extensions. - - Returns: - An HTTPX Response. - """ - return await AsyncClient.request( - self, - url=self.base_url.join(url), - method=method.value if isinstance(method, HttpMethod) else method, - content=content, - data=data, - files=files, - json=json, - params=params, - headers=headers, - cookies=cookies, - auth=auth, - follow_redirects=follow_redirects, - timeout=timeout, - extensions=None if extensions is None else dict(extensions), - ) - - async def get( # type: ignore [override] - self, - url: URLTypes, - *, - params: QueryParamTypes | None = None, - headers: HeaderTypes | None = None, - cookies: CookieTypes | None = None, - auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, - follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, - timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, - extensions: Mapping[str, Any] | None = None, - ) -> Response: - """Sends a GET request. - - Args: - url: URL or path for the request. - params: Query parameters. - headers: Request headers. - cookies: Request cookies. - auth: Auth headers. - follow_redirects: Whether to follow redirects. - timeout: Request timeout. - extensions: Dictionary of ASGI extensions. - - Returns: - An HTTPX Response. - """ - return await AsyncClient.get( - self, - url, - params=params, - headers=headers, - cookies=cookies, - auth=auth, - follow_redirects=follow_redirects, - timeout=timeout, - extensions=None if extensions is None else dict(extensions), - ) - - async def options( - self, - url: URLTypes, - *, - params: QueryParamTypes | None = None, - headers: HeaderTypes | None = None, - cookies: CookieTypes | None = None, - auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, - follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, - timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, - extensions: Mapping[str, Any] | None = None, - ) -> Response: - """Sends an OPTIONS request. - - Args: - url: URL or path for the request. - params: Query parameters. - headers: Request headers. - cookies: Request cookies. - auth: Auth headers. - follow_redirects: Whether to follow redirects. - timeout: Request timeout. - extensions: Dictionary of ASGI extensions. - - Returns: - An HTTPX Response. - """ - return await AsyncClient.options( - self, - url, - params=params, - headers=headers, - cookies=cookies, - auth=auth, - follow_redirects=follow_redirects, - timeout=timeout, - extensions=None if extensions is None else dict(extensions), - ) - - async def head( - self, - url: URLTypes, - *, - params: QueryParamTypes | None = None, - headers: HeaderTypes | None = None, - cookies: CookieTypes | None = None, - auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, - follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, - timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, - extensions: Mapping[str, Any] | None = None, - ) -> Response: - """Sends a HEAD request. - - Args: - url: URL or path for the request. - params: Query parameters. - headers: Request headers. - cookies: Request cookies. - auth: Auth headers. - follow_redirects: Whether to follow redirects. - timeout: Request timeout. - extensions: Dictionary of ASGI extensions. - - Returns: - An HTTPX Response. - """ - return await AsyncClient.head( - self, - url, - params=params, - headers=headers, - cookies=cookies, - auth=auth, - follow_redirects=follow_redirects, - timeout=timeout, - extensions=None if extensions is None else dict(extensions), - ) - - async def post( - self, - url: URLTypes, - *, - content: RequestContent | None = None, - data: RequestData | None = None, - files: RequestFiles | None = None, - json: Any | None = None, - params: QueryParamTypes | None = None, - headers: HeaderTypes | None = None, - cookies: CookieTypes | None = None, - auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, - follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, - timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, - extensions: Mapping[str, Any] | None = None, - ) -> Response: - """Sends a POST request. - - Args: - url: URL or path for the request. - content: Request content. - data: Form encoded data. - files: Multipart files to send. - json: JSON data to send. - params: Query parameters. - headers: Request headers. - cookies: Request cookies. - auth: Auth headers. - follow_redirects: Whether to follow redirects. - timeout: Request timeout. - extensions: Dictionary of ASGI extensions. - - Returns: - An HTTPX Response. - """ - return await AsyncClient.post( - self, - url, - content=content, - data=data, - files=files, - json=json, - params=params, - headers=headers, - cookies=cookies, - auth=auth, - follow_redirects=follow_redirects, - timeout=timeout, - extensions=None if extensions is None else dict(extensions), - ) - - async def put( - self, - url: URLTypes, - *, - content: RequestContent | None = None, - data: RequestData | None = None, - files: RequestFiles | None = None, - json: Any | None = None, - params: QueryParamTypes | None = None, - headers: HeaderTypes | None = None, - cookies: CookieTypes | None = None, - auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, - follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, - timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, - extensions: Mapping[str, Any] | None = None, - ) -> Response: - """Sends a PUT request. - - Args: - url: URL or path for the request. - content: Request content. - data: Form encoded data. - files: Multipart files to send. - json: JSON data to send. - params: Query parameters. - headers: Request headers. - cookies: Request cookies. - auth: Auth headers. - follow_redirects: Whether to follow redirects. - timeout: Request timeout. - extensions: Dictionary of ASGI extensions. - - Returns: - An HTTPX Response. - """ - return await AsyncClient.put( - self, - url, - content=content, - data=data, - files=files, - json=json, - params=params, - headers=headers, - cookies=cookies, - auth=auth, - follow_redirects=follow_redirects, - timeout=timeout, - extensions=None if extensions is None else dict(extensions), - ) - - async def patch( - self, - url: URLTypes, - *, - content: RequestContent | None = None, - data: RequestData | None = None, - files: RequestFiles | None = None, - json: Any | None = None, - params: QueryParamTypes | None = None, - headers: HeaderTypes | None = None, - cookies: CookieTypes | None = None, - auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, - follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, - timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, - extensions: Mapping[str, Any] | None = None, - ) -> Response: - """Sends a PATCH request. - - Args: - url: URL or path for the request. - content: Request content. - data: Form encoded data. - files: Multipart files to send. - json: JSON data to send. - params: Query parameters. - headers: Request headers. - cookies: Request cookies. - auth: Auth headers. - follow_redirects: Whether to follow redirects. - timeout: Request timeout. - extensions: Dictionary of ASGI extensions. - - Returns: - An HTTPX Response. - """ - return await AsyncClient.patch( - self, - url, - content=content, - data=data, - files=files, - json=json, - params=params, - headers=headers, - cookies=cookies, - auth=auth, - follow_redirects=follow_redirects, - timeout=timeout, - extensions=None if extensions is None else dict(extensions), - ) - - async def delete( - self, - url: URLTypes, - *, - params: QueryParamTypes | None = None, - headers: HeaderTypes | None = None, - cookies: CookieTypes | None = None, - auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, - follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, - timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, - extensions: Mapping[str, Any] | None = None, - ) -> Response: - """Sends a DELETE request. - - Args: - url: URL or path for the request. - params: Query parameters. - headers: Request headers. - cookies: Request cookies. - auth: Auth headers. - follow_redirects: Whether to follow redirects. - timeout: Request timeout. - extensions: Dictionary of ASGI extensions. - - Returns: - An HTTPX Response. - """ - return await AsyncClient.delete( - self, - url, - params=params, - headers=headers, - cookies=cookies, - auth=auth, - follow_redirects=follow_redirects, - timeout=timeout, - extensions=None if extensions is None else dict(extensions), - ) - async def websocket_connect( self, url: str, @@ -498,25 +129,19 @@ async def websocket_connect( Returns: A `WebSocketTestSession ` instance. """ - url = urljoin("ws://testserver", url) - default_headers: dict[str, str] = {} - default_headers.setdefault("connection", "upgrade") - default_headers.setdefault("sec-websocket-key", "testserver==") - default_headers.setdefault("sec-websocket-version", "13") - if subprotocols is not None: - default_headers.setdefault("sec-websocket-protocol", ", ".join(subprotocols)) try: - await AsyncClient.request( - self, - "GET", - url, - headers={**dict(headers or {}), **default_headers}, # type: ignore[misc] - params=params, - cookies=cookies, + await self.send( + self._prepare_ws_connect_request( + url=url, + subprotocols=subprotocols, + params=params, + headers=headers, + cookies=cookies, + extensions=extensions, + timeout=timeout, + ), auth=auth, follow_redirects=follow_redirects, - timeout=timeout, - extensions=None if extensions is None else dict(extensions), ) except ConnectionUpgradeExceptionError as exc: return exc.session diff --git a/litestar/testing/client/base.py b/litestar/testing/client/base.py index 3c25be117b..ddaed17935 100644 --- a/litestar/testing/client/base.py +++ b/litestar/testing/client/base.py @@ -2,11 +2,13 @@ from contextlib import contextmanager from http.cookiejar import CookieJar -from typing import TYPE_CHECKING, Any, Generator, Generic, Mapping, TypeVar, cast +from typing import TYPE_CHECKING, Any, Generator, Generic, Mapping, Sequence, TypeVar, cast from warnings import warn +import httpx from anyio.from_thread import BlockingPortal, start_blocking_portal from httpx import Cookies, Request, Response +from httpx._client import USE_CLIENT_DEFAULT, BaseClient, UseClientDefault from litestar import Litestar from litestar.connection import ASGIConnection @@ -19,7 +21,12 @@ from litestar.utils.scope.state import ScopeState if TYPE_CHECKING: - from httpx._types import CookieTypes + from httpx._types import ( + CookieTypes, + HeaderTypes, + QueryParamTypes, + TimeoutTypes, + ) from litestar.middleware.session.base import BaseBackendConfig, BaseSessionBackend from litestar.types.asgi_types import HTTPScope, Receive, Scope, Send @@ -178,3 +185,29 @@ async def _get_session_data(self) -> dict[str, Any]: cookies=dict(self.cookies), # type: ignore[arg-type] ), ) + + def _prepare_ws_connect_request( # type: ignore[misc] + self: BaseClient, # pyright: ignore + url: str, + subprotocols: Sequence[str] | None = None, + params: QueryParamTypes | None = None, + headers: HeaderTypes | None = None, + cookies: CookieTypes | None = None, + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + extensions: Mapping[str, Any] | None = None, + ) -> httpx.Request: + default_headers: dict[str, str] = {} + default_headers.setdefault("connection", "upgrade") + default_headers.setdefault("sec-websocket-key", "testserver==") + default_headers.setdefault("sec-websocket-version", "13") + if subprotocols is not None: + default_headers.setdefault("sec-websocket-protocol", ", ".join(subprotocols)) + return self.build_request( + "GET", + self.base_url.copy_with(scheme="ws").join(url), + headers={**dict(headers or {}), **default_headers}, # type: ignore[misc] + params=params, + cookies=cookies, + extensions=None if extensions is None else dict(extensions), + timeout=timeout, + ) diff --git a/litestar/testing/client/sync_client.py b/litestar/testing/client/sync_client.py index d90705646b..9cbfcb3d94 100644 --- a/litestar/testing/client/sync_client.py +++ b/litestar/testing/client/sync_client.py @@ -2,11 +2,9 @@ from contextlib import ExitStack from typing import TYPE_CHECKING, Any, Generic, Mapping, Sequence, TypeVar -from urllib.parse import urljoin -from httpx import USE_CLIENT_DEFAULT, Client, Response +from httpx import USE_CLIENT_DEFAULT, Client -from litestar import HttpMethod from litestar.testing.client.base import BaseTestClient from litestar.testing.life_span_handler import LifeSpanHandler from litestar.testing.transport import ConnectionUpgradeExceptionError, TestClientTransport @@ -19,11 +17,7 @@ CookieTypes, HeaderTypes, QueryParamTypes, - RequestContent, - RequestData, - RequestFiles, TimeoutTypes, - URLTypes, ) from typing_extensions import Self @@ -109,369 +103,6 @@ def wait_shutdown() -> None: def __exit__(self, *args: Any) -> None: self.exit_stack.close() - def request( - self, - method: str, - url: URLTypes, - *, - content: RequestContent | None = None, - data: RequestData | None = None, - files: RequestFiles | None = None, - json: Any | None = None, - params: QueryParamTypes | None = None, - headers: HeaderTypes | None = None, - cookies: CookieTypes | None = None, - auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT, - follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, - timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, - extensions: Mapping[str, Any] | None = None, - ) -> Response: - """Sends a request. - - Args: - method: An HTTP method. - url: URL or path for the request. - content: Request content. - data: Form encoded data. - files: Multipart files to send. - json: JSON data to send. - params: Query parameters. - headers: Request headers. - cookies: Request cookies. - auth: Auth headers. - follow_redirects: Whether to follow redirects. - timeout: Request timeout. - extensions: Dictionary of ASGI extensions. - - Returns: - An HTTPX Response. - """ - return Client.request( - self, - url=self.base_url.join(url), - method=method.value if isinstance(method, HttpMethod) else method, - content=content, - data=data, - files=files, - json=json, - params=params, - headers=headers, - cookies=cookies, - auth=auth, - follow_redirects=follow_redirects, - timeout=timeout, - extensions=None if extensions is None else dict(extensions), - ) - - def get( - self, - url: URLTypes, - *, - params: QueryParamTypes | None = None, - headers: HeaderTypes | None = None, - cookies: CookieTypes | None = None, - auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, - follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, - timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, - extensions: Mapping[str, Any] | None = None, - ) -> Response: - """Sends a GET request. - - Args: - url: URL or path for the request. - params: Query parameters. - headers: Request headers. - cookies: Request cookies. - auth: Auth headers. - follow_redirects: Whether to follow redirects. - timeout: Request timeout. - extensions: Dictionary of ASGI extensions. - - Returns: - An HTTPX Response. - """ - return Client.get( - self, - url, - params=params, - headers=headers, - cookies=cookies, - auth=auth, - follow_redirects=follow_redirects, - timeout=timeout, - extensions=None if extensions is None else dict(extensions), - ) - - def options( - self, - url: URLTypes, - *, - params: QueryParamTypes | None = None, - headers: HeaderTypes | None = None, - cookies: CookieTypes | None = None, - auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, - follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, - timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, - extensions: Mapping[str, Any] | None = None, - ) -> Response: - """Sends an OPTIONS request. - - Args: - url: URL or path for the request. - params: Query parameters. - headers: Request headers. - cookies: Request cookies. - auth: Auth headers. - follow_redirects: Whether to follow redirects. - timeout: Request timeout. - extensions: Dictionary of ASGI extensions. - - Returns: - An HTTPX Response. - """ - return Client.options( - self, - url, - params=params, - headers=headers, - cookies=cookies, - auth=auth, - follow_redirects=follow_redirects, - timeout=timeout, - extensions=None if extensions is None else dict(extensions), - ) - - def head( - self, - url: URLTypes, - *, - params: QueryParamTypes | None = None, - headers: HeaderTypes | None = None, - cookies: CookieTypes | None = None, - auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, - follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, - timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, - extensions: Mapping[str, Any] | None = None, - ) -> Response: - """Sends a HEAD request. - - Args: - url: URL or path for the request. - params: Query parameters. - headers: Request headers. - cookies: Request cookies. - auth: Auth headers. - follow_redirects: Whether to follow redirects. - timeout: Request timeout. - extensions: Dictionary of ASGI extensions. - - Returns: - An HTTPX Response. - """ - return Client.head( - self, - url, - params=params, - headers=headers, - cookies=cookies, - auth=auth, - follow_redirects=follow_redirects, - timeout=timeout, - extensions=None if extensions is None else dict(extensions), - ) - - def post( - self, - url: URLTypes, - *, - content: RequestContent | None = None, - data: RequestData | None = None, - files: RequestFiles | None = None, - json: Any | None = None, - params: QueryParamTypes | None = None, - headers: HeaderTypes | None = None, - cookies: CookieTypes | None = None, - auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, - follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, - timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, - extensions: Mapping[str, Any] | None = None, - ) -> Response: - """Sends a POST request. - - Args: - url: URL or path for the request. - content: Request content. - data: Form encoded data. - files: Multipart files to send. - json: JSON data to send. - params: Query parameters. - headers: Request headers. - cookies: Request cookies. - auth: Auth headers. - follow_redirects: Whether to follow redirects. - timeout: Request timeout. - extensions: Dictionary of ASGI extensions. - - Returns: - An HTTPX Response. - """ - return Client.post( - self, - url, - content=content, - data=data, - files=files, - json=json, - params=params, - headers=headers, - cookies=cookies, - auth=auth, - follow_redirects=follow_redirects, - timeout=timeout, - extensions=None if extensions is None else dict(extensions), - ) - - def put( - self, - url: URLTypes, - *, - content: RequestContent | None = None, - data: RequestData | None = None, - files: RequestFiles | None = None, - json: Any | None = None, - params: QueryParamTypes | None = None, - headers: HeaderTypes | None = None, - cookies: CookieTypes | None = None, - auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, - follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, - timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, - extensions: Mapping[str, Any] | None = None, - ) -> Response: - """Sends a PUT request. - - Args: - url: URL or path for the request. - content: Request content. - data: Form encoded data. - files: Multipart files to send. - json: JSON data to send. - params: Query parameters. - headers: Request headers. - cookies: Request cookies. - auth: Auth headers. - follow_redirects: Whether to follow redirects. - timeout: Request timeout. - extensions: Dictionary of ASGI extensions. - - Returns: - An HTTPX Response. - """ - return Client.put( - self, - url, - content=content, - data=data, - files=files, - json=json, - params=params, - headers=headers, - cookies=cookies, - auth=auth, - follow_redirects=follow_redirects, - timeout=timeout, - extensions=None if extensions is None else dict(extensions), - ) - - def patch( - self, - url: URLTypes, - *, - content: RequestContent | None = None, - data: RequestData | None = None, - files: RequestFiles | None = None, - json: Any | None = None, - params: QueryParamTypes | None = None, - headers: HeaderTypes | None = None, - cookies: CookieTypes | None = None, - auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, - follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, - timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, - extensions: Mapping[str, Any] | None = None, - ) -> Response: - """Sends a PATCH request. - - Args: - url: URL or path for the request. - content: Request content. - data: Form encoded data. - files: Multipart files to send. - json: JSON data to send. - params: Query parameters. - headers: Request headers. - cookies: Request cookies. - auth: Auth headers. - follow_redirects: Whether to follow redirects. - timeout: Request timeout. - extensions: Dictionary of ASGI extensions. - - Returns: - An HTTPX Response. - """ - return Client.patch( - self, - url, - content=content, - data=data, - files=files, - json=json, - params=params, - headers=headers, - cookies=cookies, - auth=auth, - follow_redirects=follow_redirects, - timeout=timeout, - extensions=None if extensions is None else dict(extensions), - ) - - def delete( - self, - url: URLTypes, - *, - params: QueryParamTypes | None = None, - headers: HeaderTypes | None = None, - cookies: CookieTypes | None = None, - auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT, - follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT, - timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, - extensions: Mapping[str, Any] | None = None, - ) -> Response: - """Sends a DELETE request. - - Args: - url: URL or path for the request. - params: Query parameters. - headers: Request headers. - cookies: Request cookies. - auth: Auth headers. - follow_redirects: Whether to follow redirects. - timeout: Request timeout. - extensions: Dictionary of ASGI extensions. - - Returns: - An HTTPX Response. - """ - return Client.delete( - self, - url, - params=params, - headers=headers, - cookies=cookies, - auth=auth, - follow_redirects=follow_redirects, - timeout=timeout, - extensions=None if extensions is None else dict(extensions), - ) - def websocket_connect( self, url: str, @@ -500,25 +131,19 @@ def websocket_connect( Returns: A `WebSocketTestSession ` instance. """ - url = urljoin("ws://testserver", url) - default_headers: dict[str, str] = {} - default_headers.setdefault("connection", "upgrade") - default_headers.setdefault("sec-websocket-key", "testserver==") - default_headers.setdefault("sec-websocket-version", "13") - if subprotocols is not None: - default_headers.setdefault("sec-websocket-protocol", ", ".join(subprotocols)) try: - Client.request( - self, - "GET", - url, - headers={**dict(headers or {}), **default_headers}, # type: ignore[misc] - params=params, - cookies=cookies, + self.send( + self._prepare_ws_connect_request( + url=url, + subprotocols=subprotocols, + params=params, + headers=headers, + cookies=cookies, + extensions=extensions, + timeout=timeout, + ), auth=auth, follow_redirects=follow_redirects, - timeout=timeout, - extensions=None if extensions is None else dict(extensions), ) except ConnectionUpgradeExceptionError as exc: return exc.session diff --git a/litestar/testing/request_factory.py b/litestar/testing/request_factory.py index 49f2420b23..8899a94ad9 100644 --- a/litestar/testing/request_factory.py +++ b/litestar/testing/request_factory.py @@ -14,7 +14,7 @@ from litestar.enums import HttpMethod, ParamType, RequestEncodingType, ScopeType from litestar.handlers.http_handlers import get from litestar.serialization import decode_json, default_serializer, encode_json -from litestar.types import DataContainerType, HTTPScope, RouteHandlerType +from litestar.types import DataContainerType, HTTPHandlerDecorator, HTTPScope, RouteHandlerType from litestar.types.asgi_types import ASGIVersion from litestar.utils import get_serializer_from_scope from litestar.utils.scope.state import ScopeState @@ -25,7 +25,7 @@ from litestar.datastructures.cookie import Cookie from litestar.handlers.http_handlers import HTTPRouteHandler -_decorator_http_method_map: dict[HttpMethod, type[HTTPRouteHandler]] = { +_decorator_http_method_map: dict[HttpMethod, HTTPHandlerDecorator] = { HttpMethod.GET: get, HttpMethod.POST: post, HttpMethod.DELETE: delete, diff --git a/litestar/testing/transport.py b/litestar/testing/transport.py index ffa76a46ac..7a965dc9fd 100644 --- a/litestar/testing/transport.py +++ b/litestar/testing/transport.py @@ -168,7 +168,7 @@ def handle_request(self, request: Request) -> Response: self.create_receive(request=request, context=context), self.create_send(request=request, context=context), ) - except BaseException as exc: # noqa: BLE001 + except BaseException as exc: if self.raise_server_exceptions: raise exc return Response( diff --git a/litestar/types/__init__.py b/litestar/types/__init__.py index 90e319277c..1e5197f605 100644 --- a/litestar/types/__init__.py +++ b/litestar/types/__init__.py @@ -54,6 +54,7 @@ ExceptionHandler, GetLogger, Guard, + HTTPHandlerDecorator, LifespanHook, OnAppInitHandler, OperationIDCreator, @@ -96,6 +97,7 @@ "DataContainerType", "DataclassProtocol", "Dependencies", + "HTTPHandlerDecorator", "Empty", "EmptyType", "ExceptionHandler", diff --git a/litestar/types/callable_types.py b/litestar/types/callable_types.py index 36055d7199..0f07295cc4 100644 --- a/litestar/types/callable_types.py +++ b/litestar/types/callable_types.py @@ -38,3 +38,4 @@ OnAppInitHandler: TypeAlias = "Callable[[AppConfig], AppConfig]" OperationIDCreator: TypeAlias = "Callable[[HTTPRouteHandler, Method, list[str | PathParameterDefinition]], str]" Serializer: TypeAlias = Callable[[Any], Any] +HTTPHandlerDecorator: TypeAlias = "Callable[..., Callable[[AnyCallable], HTTPRouteHandler]]" diff --git a/litestar/utils/scope/state.py b/litestar/utils/scope/state.py index 48fd192c27..873fef0efc 100644 --- a/litestar/utils/scope/state.py +++ b/litestar/utils/scope/state.py @@ -43,6 +43,7 @@ class ScopeState: "msgpack", "parsed_query", "response_compressed", + "response_started", "session_id", "url", ) @@ -66,6 +67,7 @@ def __init__(self) -> None: self.msgpack = Empty self.parsed_query = Empty self.response_compressed = Empty + self.response_started = False self.session_id = Empty self.url = Empty @@ -87,6 +89,7 @@ def __init__(self) -> None: msgpack: Any | EmptyType parsed_query: tuple[tuple[str, str], ...] | EmptyType response_compressed: bool | EmptyType + response_started: bool session_id: str | None | EmptyType url: URL | EmptyType diff --git a/pdm.lock b/pdm.lock index 4caf2e04c3..61f47e743a 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,11 +5,11 @@ groups = ["default", "annotated-types", "attrs", "brotli", "cli", "cryptography", "dev", "dev-contrib", "docs", "full", "jinja", "jwt", "linting", "mako", "minijinja", "opentelemetry", "piccolo", "picologging", "prometheus", "pydantic", "redis", "sqlalchemy", "standard", "structlog", "test"] strategy = ["cross_platform", "inherit_metadata"] lock_version = "4.4.1" -content_hash = "sha256:e5803ff230565dd1f271d57731ccb117b91bf050b9129a398d1ba674c0031606" +content_hash = "sha256:78a8a48fecdec8138d11f42b01c0794b51e32ca736c58443c1c1f222f082d88a" [[package]] name = "advanced-alchemy" -version = "0.8.4" +version = "0.11.0" requires_python = ">=3.8" summary = "Ready-to-go SQLAlchemy concoctions." groups = ["docs", "full", "sqlalchemy"] @@ -20,8 +20,8 @@ dependencies = [ "typing-extensions>=4.0.0", ] files = [ - {file = "advanced_alchemy-0.8.4-py3-none-any.whl", hash = "sha256:0ad4e2e33cfa6f8641ea7e9df50c8dbb18890000080b74d85305ba8821c2ce50"}, - {file = "advanced_alchemy-0.8.4.tar.gz", hash = "sha256:6436ebad0e0b92ef5093bf425961f5469020035c65953ff7f619aef24a08fc71"}, + {file = "advanced_alchemy-0.11.0-py3-none-any.whl", hash = "sha256:ed32c20ad02923060542c00678d90efbab914226792b62f2c6928e89acaf7ab2"}, + {file = "advanced_alchemy-0.11.0.tar.gz", hash = "sha256:bdc2c81067b5b6c3d22d1d4869b40a471b4818b536f15c018164e9de08d65a0b"}, ] [[package]] @@ -69,7 +69,7 @@ files = [ [[package]] name = "annotated-types" -version = "0.6.0" +version = "0.7.0" requires_python = ">=3.8" summary = "Reusable constraint types to use with typing.Annotated" groups = ["annotated-types", "dev", "docs", "full", "piccolo", "pydantic"] @@ -77,8 +77,8 @@ dependencies = [ "typing-extensions>=4.0.0; python_version < \"3.9\"", ] files = [ - {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, - {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, ] [[package]] @@ -311,16 +311,16 @@ files = [ [[package]] name = "babel" -version = "2.14.0" -requires_python = ">=3.7" +version = "2.15.0" +requires_python = ">=3.8" summary = "Internationalization utilities" groups = ["docs"] dependencies = [ "pytz>=2015.7; python_version < \"3.9\"", ] files = [ - {file = "Babel-2.14.0-py3-none-any.whl", hash = "sha256:efb1a25b7118e67ce3a259bed20545c29cb68be8ad2c784c83689981b7a57287"}, - {file = "Babel-2.14.0.tar.gz", hash = "sha256:6919867db036398ba21eb5c7a0f6b28ab8cbc3ae7a73a44ebe34ae74a4e7d363"}, + {file = "Babel-2.15.0-py3-none-any.whl", hash = "sha256:08706bdad8d0a3413266ab61bd6c34d0c28d6e1e7badf40a2cebe67644e2e1fb"}, + {file = "babel-2.15.0.tar.gz", hash = "sha256:8daf0e265d05768bc6c7a314cf1321e9a123afc328cc635c18622a2f30a04413"}, ] [[package]] @@ -341,8 +341,8 @@ files = [ [[package]] name = "beanie" -version = "1.25.0" -requires_python = ">=3.7,<4.0" +version = "1.26.0" +requires_python = "<4.0,>=3.7" summary = "Asynchronous Python ODM for MongoDB" groups = ["dev"] dependencies = [ @@ -354,8 +354,8 @@ dependencies = [ "typing-extensions>=4.7; python_version < \"3.11\"", ] files = [ - {file = "beanie-1.25.0-py3-none-any.whl", hash = "sha256:4436ac740718ccd62b21576778679ac972359fce2938557890c576adbbf5e244"}, - {file = "beanie-1.25.0.tar.gz", hash = "sha256:f153866b9ba015274102e10a397602d088fc039b705bd806cb447c898cd2979b"}, + {file = "beanie-1.26.0-py3-none-any.whl", hash = "sha256:b45926c01d4a899c519c665c2a5f230990717e99f7fd68172a389ca33e7693b9"}, + {file = "beanie-1.26.0.tar.gz", hash = "sha256:54016f4ec71ed0ea6ce0c7946a395090c45687f254dbbe1cf06eec608383f790"}, ] [[package]] @@ -374,7 +374,7 @@ files = [ [[package]] name = "black" -version = "24.3.0" +version = "24.4.2" requires_python = ">=3.8" summary = "The uncompromising code formatter." groups = ["docs", "full", "piccolo"] @@ -388,28 +388,28 @@ dependencies = [ "typing-extensions>=4.0.1; python_version < \"3.11\"", ] files = [ - {file = "black-24.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7d5e026f8da0322b5662fa7a8e752b3fa2dac1c1cbc213c3d7ff9bdd0ab12395"}, - {file = "black-24.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9f50ea1132e2189d8dff0115ab75b65590a3e97de1e143795adb4ce317934995"}, - {file = "black-24.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2af80566f43c85f5797365077fb64a393861a3730bd110971ab7a0c94e873e7"}, - {file = "black-24.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:4be5bb28e090456adfc1255e03967fb67ca846a03be7aadf6249096100ee32d0"}, - {file = "black-24.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f1373a7808a8f135b774039f61d59e4be7eb56b2513d3d2f02a8b9365b8a8a9"}, - {file = "black-24.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aadf7a02d947936ee418777e0247ea114f78aff0d0959461057cae8a04f20597"}, - {file = "black-24.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c02e4ea2ae09d16314d30912a58ada9a5c4fdfedf9512d23326128ac08ac3d"}, - {file = "black-24.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:bf21b7b230718a5f08bd32d5e4f1db7fc8788345c8aea1d155fc17852b3410f5"}, - {file = "black-24.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2818cf72dfd5d289e48f37ccfa08b460bf469e67fb7c4abb07edc2e9f16fb63f"}, - {file = "black-24.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4acf672def7eb1725f41f38bf6bf425c8237248bb0804faa3965c036f7672d11"}, - {file = "black-24.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7ed6668cbbfcd231fa0dc1b137d3e40c04c7f786e626b405c62bcd5db5857e4"}, - {file = "black-24.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:56f52cfbd3dabe2798d76dbdd299faa046a901041faf2cf33288bc4e6dae57b5"}, - {file = "black-24.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:79dcf34b33e38ed1b17434693763301d7ccbd1c5860674a8f871bd15139e7837"}, - {file = "black-24.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e19cb1c6365fd6dc38a6eae2dcb691d7d83935c10215aef8e6c38edee3f77abd"}, - {file = "black-24.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b76c275e4c1c5ce6e9870911384bff5ca31ab63d19c76811cb1fb162678213"}, - {file = "black-24.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b5991d523eee14756f3c8d5df5231550ae8993e2286b8014e2fdea7156ed0959"}, - {file = "black-24.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c45f8dff244b3c431b36e3224b6be4a127c6aca780853574c00faf99258041eb"}, - {file = "black-24.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6905238a754ceb7788a73f02b45637d820b2f5478b20fec82ea865e4f5d4d9f7"}, - {file = "black-24.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7de8d330763c66663661a1ffd432274a2f92f07feeddd89ffd085b5744f85e7"}, - {file = "black-24.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:7bb041dca0d784697af4646d3b62ba4a6b028276ae878e53f6b4f74ddd6db99f"}, - {file = "black-24.3.0-py3-none-any.whl", hash = "sha256:41622020d7120e01d377f74249e677039d20e6344ff5851de8a10f11f513bf93"}, - {file = "black-24.3.0.tar.gz", hash = "sha256:a0c9c4a0771afc6919578cec71ce82a3e31e054904e7197deacbc9382671c41f"}, + {file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"}, + {file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"}, + {file = "black-24.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063"}, + {file = "black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96"}, + {file = "black-24.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474"}, + {file = "black-24.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c"}, + {file = "black-24.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb"}, + {file = "black-24.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1"}, + {file = "black-24.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"}, + {file = "black-24.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04"}, + {file = "black-24.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc"}, + {file = "black-24.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0"}, + {file = "black-24.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7"}, + {file = "black-24.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94"}, + {file = "black-24.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8"}, + {file = "black-24.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c"}, + {file = "black-24.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1"}, + {file = "black-24.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741"}, + {file = "black-24.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"}, + {file = "black-24.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7"}, + {file = "black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c"}, + {file = "black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d"}, ] [[package]] @@ -715,7 +715,7 @@ files = [ [[package]] name = "codecov-cli" -version = "0.5.0" +version = "0.6.0" requires_python = ">=3.8" summary = "Codecov Command Line Interface" groups = ["linting"] @@ -724,14 +724,16 @@ dependencies = [ "httpx==0.23.*", "ijson==3.*", "pyyaml==6.*", + "regex", "responses==0.21.*", + "test-results-parser==0.1.*", "tree-sitter==0.20.*", ] files = [ - {file = "codecov-cli-0.5.0.tar.gz", hash = "sha256:11dfd62eca5a2badab4a72d920b0b362ae82a76d60ea573652996308c9e29dd5"}, - {file = "codecov_cli-0.5.0-cp311-cp311-macosx_12_6_x86_64.whl", hash = "sha256:a2aa22ce477fc998b21af1a617edb3fb5a62e806890b2d72184ec19bf7417af2"}, - {file = "codecov_cli-0.5.0-cp311-cp311-manylinux2014_x86_64.whl", hash = "sha256:faa7bb96c7715aa025ddaa5b0166e7e8cb48aa3de23f9a089a8422da18fe5964"}, - {file = "codecov_cli-0.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:a8ad69987389f15c131f5774f3b2a63c01840f10a67a7d6a26c41193d776f654"}, + {file = "codecov-cli-0.6.0.tar.gz", hash = "sha256:25d23c14d99f58c071d1db4d78aa9774a7407054cef49ca676ad805d1daaefe1"}, + {file = "codecov_cli-0.6.0-cp311-cp311-macosx_12_6_x86_64.whl", hash = "sha256:0bee51b94bd30f194962069e504dd8d4754c805839218ba02a1d773b204b37d6"}, + {file = "codecov_cli-0.6.0-cp311-cp311-manylinux2014_x86_64.whl", hash = "sha256:ce56f1d75504725a12f533cb552d9141ac3d6574c868f2357a038019870a16eb"}, + {file = "codecov_cli-0.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:3a51f4465f33d452e5dec99fbed1c975e40110dfe68432fe0e296118329e51ea"}, ] [[package]] @@ -772,134 +774,134 @@ files = [ [[package]] name = "coverage" -version = "7.4.4" +version = "7.5.1" requires_python = ">=3.8" summary = "Code coverage measurement for Python" groups = ["test"] files = [ - {file = "coverage-7.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0be5efd5127542ef31f165de269f77560d6cdef525fffa446de6f7e9186cfb2"}, - {file = "coverage-7.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ccd341521be3d1b3daeb41960ae94a5e87abe2f46f17224ba5d6f2b8398016cf"}, - {file = "coverage-7.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fa497a8ab37784fbb20ab699c246053ac294d13fc7eb40ec007a5043ec91f8"}, - {file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b1a93009cb80730c9bca5d6d4665494b725b6e8e157c1cb7f2db5b4b122ea562"}, - {file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:690db6517f09336559dc0b5f55342df62370a48f5469fabf502db2c6d1cffcd2"}, - {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:09c3255458533cb76ef55da8cc49ffab9e33f083739c8bd4f58e79fecfe288f7"}, - {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8ce1415194b4a6bd0cdcc3a1dfbf58b63f910dcb7330fe15bdff542c56949f87"}, - {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b91cbc4b195444e7e258ba27ac33769c41b94967919f10037e6355e998af255c"}, - {file = "coverage-7.4.4-cp310-cp310-win32.whl", hash = "sha256:598825b51b81c808cb6f078dcb972f96af96b078faa47af7dfcdf282835baa8d"}, - {file = "coverage-7.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:09ef9199ed6653989ebbcaacc9b62b514bb63ea2f90256e71fea3ed74bd8ff6f"}, - {file = "coverage-7.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0f9f50e7ef2a71e2fae92774c99170eb8304e3fdf9c8c3c7ae9bab3e7229c5cf"}, - {file = "coverage-7.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:623512f8ba53c422fcfb2ce68362c97945095b864cda94a92edbaf5994201083"}, - {file = "coverage-7.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0513b9508b93da4e1716744ef6ebc507aff016ba115ffe8ecff744d1322a7b63"}, - {file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40209e141059b9370a2657c9b15607815359ab3ef9918f0196b6fccce8d3230f"}, - {file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a2b2b78c78293782fd3767d53e6474582f62443d0504b1554370bde86cc8227"}, - {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:73bfb9c09951125d06ee473bed216e2c3742f530fc5acc1383883125de76d9cd"}, - {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f384c3cc76aeedce208643697fb3e8437604b512255de6d18dae3f27655a384"}, - {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:54eb8d1bf7cacfbf2a3186019bcf01d11c666bd495ed18717162f7eb1e9dd00b"}, - {file = "coverage-7.4.4-cp311-cp311-win32.whl", hash = "sha256:cac99918c7bba15302a2d81f0312c08054a3359eaa1929c7e4b26ebe41e9b286"}, - {file = "coverage-7.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:b14706df8b2de49869ae03a5ccbc211f4041750cd4a66f698df89d44f4bd30ec"}, - {file = "coverage-7.4.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:201bef2eea65e0e9c56343115ba3814e896afe6d36ffd37bab783261db430f76"}, - {file = "coverage-7.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41c9c5f3de16b903b610d09650e5e27adbfa7f500302718c9ffd1c12cf9d6818"}, - {file = "coverage-7.4.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d898fe162d26929b5960e4e138651f7427048e72c853607f2b200909794ed978"}, - {file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ea79bb50e805cd6ac058dfa3b5c8f6c040cb87fe83de10845857f5535d1db70"}, - {file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce4b94265ca988c3f8e479e741693d143026632672e3ff924f25fab50518dd51"}, - {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:00838a35b882694afda09f85e469c96367daa3f3f2b097d846a7216993d37f4c"}, - {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fdfafb32984684eb03c2d83e1e51f64f0906b11e64482df3c5db936ce3839d48"}, - {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:69eb372f7e2ece89f14751fbcbe470295d73ed41ecd37ca36ed2eb47512a6ab9"}, - {file = "coverage-7.4.4-cp312-cp312-win32.whl", hash = "sha256:137eb07173141545e07403cca94ab625cc1cc6bc4c1e97b6e3846270e7e1fea0"}, - {file = "coverage-7.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:d71eec7d83298f1af3326ce0ff1d0ea83c7cb98f72b577097f9083b20bdaf05e"}, - {file = "coverage-7.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ae728ff3b5401cc320d792866987e7e7e880e6ebd24433b70a33b643bb0384"}, - {file = "coverage-7.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc4f1358cb0c78edef3ed237ef2c86056206bb8d9140e73b6b89fbcfcbdd40e1"}, - {file = "coverage-7.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8130a2aa2acb8788e0b56938786c33c7c98562697bf9f4c7d6e8e5e3a0501e4a"}, - {file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf271892d13e43bc2b51e6908ec9a6a5094a4df1d8af0bfc360088ee6c684409"}, - {file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4cdc86d54b5da0df6d3d3a2f0b710949286094c3a6700c21e9015932b81447e"}, - {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ae71e7ddb7a413dd60052e90528f2f65270aad4b509563af6d03d53e979feafd"}, - {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:38dd60d7bf242c4ed5b38e094baf6401faa114fc09e9e6632374388a404f98e7"}, - {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa5b1c1bfc28384f1f53b69a023d789f72b2e0ab1b3787aae16992a7ca21056c"}, - {file = "coverage-7.4.4-cp38-cp38-win32.whl", hash = "sha256:dfa8fe35a0bb90382837b238fff375de15f0dcdb9ae68ff85f7a63649c98527e"}, - {file = "coverage-7.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:b2991665420a803495e0b90a79233c1433d6ed77ef282e8e152a324bbbc5e0c8"}, - {file = "coverage-7.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b799445b9f7ee8bf299cfaed6f5b226c0037b74886a4e11515e569b36fe310d"}, - {file = "coverage-7.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b4d33f418f46362995f1e9d4f3a35a1b6322cb959c31d88ae56b0298e1c22357"}, - {file = "coverage-7.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aadacf9a2f407a4688d700e4ebab33a7e2e408f2ca04dbf4aef17585389eff3e"}, - {file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c95949560050d04d46b919301826525597f07b33beba6187d04fa64d47ac82e"}, - {file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff7687ca3d7028d8a5f0ebae95a6e4827c5616b31a4ee1192bdfde697db110d4"}, - {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5fc1de20b2d4a061b3df27ab9b7c7111e9a710f10dc2b84d33a4ab25065994ec"}, - {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c74880fc64d4958159fbd537a091d2a585448a8f8508bf248d72112723974cbd"}, - {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:742a76a12aa45b44d236815d282b03cfb1de3b4323f3e4ec933acfae08e54ade"}, - {file = "coverage-7.4.4-cp39-cp39-win32.whl", hash = "sha256:d89d7b2974cae412400e88f35d86af72208e1ede1a541954af5d944a8ba46c57"}, - {file = "coverage-7.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:9ca28a302acb19b6af89e90f33ee3e1906961f94b54ea37de6737b7ca9d8827c"}, - {file = "coverage-7.4.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:b2c5edc4ac10a7ef6605a966c58929ec6c1bd0917fb8c15cb3363f65aa40e677"}, - {file = "coverage-7.4.4.tar.gz", hash = "sha256:c901df83d097649e257e803be22592aedfd5182f07b3cc87d640bbb9afd50f49"}, + {file = "coverage-7.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0884920835a033b78d1c73b6d3bbcda8161a900f38a488829a83982925f6c2e"}, + {file = "coverage-7.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:39afcd3d4339329c5f58de48a52f6e4e50f6578dd6099961cf22228feb25f38f"}, + {file = "coverage-7.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a7b0ceee8147444347da6a66be737c9d78f3353b0681715b668b72e79203e4a"}, + {file = "coverage-7.5.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a9ca3f2fae0088c3c71d743d85404cec8df9be818a005ea065495bedc33da35"}, + {file = "coverage-7.5.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd215c0c7d7aab005221608a3c2b46f58c0285a819565887ee0b718c052aa4e"}, + {file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4bf0655ab60d754491004a5efd7f9cccefcc1081a74c9ef2da4735d6ee4a6223"}, + {file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:61c4bf1ba021817de12b813338c9be9f0ad5b1e781b9b340a6d29fc13e7c1b5e"}, + {file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:db66fc317a046556a96b453a58eced5024af4582a8dbdc0c23ca4dbc0d5b3146"}, + {file = "coverage-7.5.1-cp310-cp310-win32.whl", hash = "sha256:b016ea6b959d3b9556cb401c55a37547135a587db0115635a443b2ce8f1c7228"}, + {file = "coverage-7.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:df4e745a81c110e7446b1cc8131bf986157770fa405fe90e15e850aaf7619bc8"}, + {file = "coverage-7.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:796a79f63eca8814ca3317a1ea443645c9ff0d18b188de470ed7ccd45ae79428"}, + {file = "coverage-7.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4fc84a37bfd98db31beae3c2748811a3fa72bf2007ff7902f68746d9757f3746"}, + {file = "coverage-7.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6175d1a0559986c6ee3f7fccfc4a90ecd12ba0a383dcc2da30c2b9918d67d8a3"}, + {file = "coverage-7.5.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fc81d5878cd6274ce971e0a3a18a8803c3fe25457165314271cf78e3aae3aa2"}, + {file = "coverage-7.5.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:556cf1a7cbc8028cb60e1ff0be806be2eded2daf8129b8811c63e2b9a6c43bca"}, + {file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9981706d300c18d8b220995ad22627647be11a4276721c10911e0e9fa44c83e8"}, + {file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d7fed867ee50edf1a0b4a11e8e5d0895150e572af1cd6d315d557758bfa9c057"}, + {file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ef48e2707fb320c8f139424a596f5b69955a85b178f15af261bab871873bb987"}, + {file = "coverage-7.5.1-cp311-cp311-win32.whl", hash = "sha256:9314d5678dcc665330df5b69c1e726a0e49b27df0461c08ca12674bcc19ef136"}, + {file = "coverage-7.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:5fa567e99765fe98f4e7d7394ce623e794d7cabb170f2ca2ac5a4174437e90dd"}, + {file = "coverage-7.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b6cf3764c030e5338e7f61f95bd21147963cf6aa16e09d2f74f1fa52013c1206"}, + {file = "coverage-7.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ec92012fefebee89a6b9c79bc39051a6cb3891d562b9270ab10ecfdadbc0c34"}, + {file = "coverage-7.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16db7f26000a07efcf6aea00316f6ac57e7d9a96501e990a36f40c965ec7a95d"}, + {file = "coverage-7.5.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:beccf7b8a10b09c4ae543582c1319c6df47d78fd732f854ac68d518ee1fb97fa"}, + {file = "coverage-7.5.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8748731ad392d736cc9ccac03c9845b13bb07d020a33423fa5b3a36521ac6e4e"}, + {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7352b9161b33fd0b643ccd1f21f3a3908daaddf414f1c6cb9d3a2fd618bf2572"}, + {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:7a588d39e0925f6a2bff87154752481273cdb1736270642aeb3635cb9b4cad07"}, + {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:68f962d9b72ce69ea8621f57551b2fa9c70509af757ee3b8105d4f51b92b41a7"}, + {file = "coverage-7.5.1-cp312-cp312-win32.whl", hash = "sha256:f152cbf5b88aaeb836127d920dd0f5e7edff5a66f10c079157306c4343d86c19"}, + {file = "coverage-7.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:5a5740d1fb60ddf268a3811bcd353de34eb56dc24e8f52a7f05ee513b2d4f596"}, + {file = "coverage-7.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e2213def81a50519d7cc56ed643c9e93e0247f5bbe0d1247d15fa520814a7cd7"}, + {file = "coverage-7.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5037f8fcc2a95b1f0e80585bd9d1ec31068a9bcb157d9750a172836e98bc7a90"}, + {file = "coverage-7.5.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3721c2c9e4c4953a41a26c14f4cef64330392a6d2d675c8b1db3b645e31f0e"}, + {file = "coverage-7.5.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca498687ca46a62ae590253fba634a1fe9836bc56f626852fb2720f334c9e4e5"}, + {file = "coverage-7.5.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cdcbc320b14c3e5877ee79e649677cb7d89ef588852e9583e6b24c2e5072661"}, + {file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:57e0204b5b745594e5bc14b9b50006da722827f0b8c776949f1135677e88d0b8"}, + {file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fe7502616b67b234482c3ce276ff26f39ffe88adca2acf0261df4b8454668b4"}, + {file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9e78295f4144f9dacfed4f92935fbe1780021247c2fabf73a819b17f0ccfff8d"}, + {file = "coverage-7.5.1-cp38-cp38-win32.whl", hash = "sha256:1434e088b41594baa71188a17533083eabf5609e8e72f16ce8c186001e6b8c41"}, + {file = "coverage-7.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:0646599e9b139988b63704d704af8e8df7fa4cbc4a1f33df69d97f36cb0a38de"}, + {file = "coverage-7.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4cc37def103a2725bc672f84bd939a6fe4522310503207aae4d56351644682f1"}, + {file = "coverage-7.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc0b4d8bfeabd25ea75e94632f5b6e047eef8adaed0c2161ada1e922e7f7cece"}, + {file = "coverage-7.5.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d0a0f5e06881ecedfe6f3dd2f56dcb057b6dbeb3327fd32d4b12854df36bf26"}, + {file = "coverage-7.5.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9735317685ba6ec7e3754798c8871c2f49aa5e687cc794a0b1d284b2389d1bd5"}, + {file = "coverage-7.5.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d21918e9ef11edf36764b93101e2ae8cc82aa5efdc7c5a4e9c6c35a48496d601"}, + {file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c3e757949f268364b96ca894b4c342b41dc6f8f8b66c37878aacef5930db61be"}, + {file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:79afb6197e2f7f60c4824dd4b2d4c2ec5801ceb6ba9ce5d2c3080e5660d51a4f"}, + {file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d1d0d98d95dd18fe29dc66808e1accf59f037d5716f86a501fc0256455219668"}, + {file = "coverage-7.5.1-cp39-cp39-win32.whl", hash = "sha256:1cc0fe9b0b3a8364093c53b0b4c0c2dd4bb23acbec4c9240b5f284095ccf7981"}, + {file = "coverage-7.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:dde0070c40ea8bb3641e811c1cfbf18e265d024deff6de52c5950677a8fb1e0f"}, + {file = "coverage-7.5.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:6537e7c10cc47c595828b8a8be04c72144725c383c4702703ff4e42e44577312"}, + {file = "coverage-7.5.1.tar.gz", hash = "sha256:54de9ef3a9da981f7af93eafde4ede199e0846cd819eb27c88e2b712aae9708c"}, ] [[package]] name = "coverage" -version = "7.4.4" +version = "7.5.1" extras = ["toml"] requires_python = ">=3.8" summary = "Code coverage measurement for Python" groups = ["test"] dependencies = [ - "coverage==7.4.4", + "coverage==7.5.1", "tomli; python_full_version <= \"3.11.0a6\"", ] files = [ - {file = "coverage-7.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0be5efd5127542ef31f165de269f77560d6cdef525fffa446de6f7e9186cfb2"}, - {file = "coverage-7.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ccd341521be3d1b3daeb41960ae94a5e87abe2f46f17224ba5d6f2b8398016cf"}, - {file = "coverage-7.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fa497a8ab37784fbb20ab699c246053ac294d13fc7eb40ec007a5043ec91f8"}, - {file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b1a93009cb80730c9bca5d6d4665494b725b6e8e157c1cb7f2db5b4b122ea562"}, - {file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:690db6517f09336559dc0b5f55342df62370a48f5469fabf502db2c6d1cffcd2"}, - {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:09c3255458533cb76ef55da8cc49ffab9e33f083739c8bd4f58e79fecfe288f7"}, - {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8ce1415194b4a6bd0cdcc3a1dfbf58b63f910dcb7330fe15bdff542c56949f87"}, - {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b91cbc4b195444e7e258ba27ac33769c41b94967919f10037e6355e998af255c"}, - {file = "coverage-7.4.4-cp310-cp310-win32.whl", hash = "sha256:598825b51b81c808cb6f078dcb972f96af96b078faa47af7dfcdf282835baa8d"}, - {file = "coverage-7.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:09ef9199ed6653989ebbcaacc9b62b514bb63ea2f90256e71fea3ed74bd8ff6f"}, - {file = "coverage-7.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0f9f50e7ef2a71e2fae92774c99170eb8304e3fdf9c8c3c7ae9bab3e7229c5cf"}, - {file = "coverage-7.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:623512f8ba53c422fcfb2ce68362c97945095b864cda94a92edbaf5994201083"}, - {file = "coverage-7.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0513b9508b93da4e1716744ef6ebc507aff016ba115ffe8ecff744d1322a7b63"}, - {file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40209e141059b9370a2657c9b15607815359ab3ef9918f0196b6fccce8d3230f"}, - {file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a2b2b78c78293782fd3767d53e6474582f62443d0504b1554370bde86cc8227"}, - {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:73bfb9c09951125d06ee473bed216e2c3742f530fc5acc1383883125de76d9cd"}, - {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f384c3cc76aeedce208643697fb3e8437604b512255de6d18dae3f27655a384"}, - {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:54eb8d1bf7cacfbf2a3186019bcf01d11c666bd495ed18717162f7eb1e9dd00b"}, - {file = "coverage-7.4.4-cp311-cp311-win32.whl", hash = "sha256:cac99918c7bba15302a2d81f0312c08054a3359eaa1929c7e4b26ebe41e9b286"}, - {file = "coverage-7.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:b14706df8b2de49869ae03a5ccbc211f4041750cd4a66f698df89d44f4bd30ec"}, - {file = "coverage-7.4.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:201bef2eea65e0e9c56343115ba3814e896afe6d36ffd37bab783261db430f76"}, - {file = "coverage-7.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41c9c5f3de16b903b610d09650e5e27adbfa7f500302718c9ffd1c12cf9d6818"}, - {file = "coverage-7.4.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d898fe162d26929b5960e4e138651f7427048e72c853607f2b200909794ed978"}, - {file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ea79bb50e805cd6ac058dfa3b5c8f6c040cb87fe83de10845857f5535d1db70"}, - {file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce4b94265ca988c3f8e479e741693d143026632672e3ff924f25fab50518dd51"}, - {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:00838a35b882694afda09f85e469c96367daa3f3f2b097d846a7216993d37f4c"}, - {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fdfafb32984684eb03c2d83e1e51f64f0906b11e64482df3c5db936ce3839d48"}, - {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:69eb372f7e2ece89f14751fbcbe470295d73ed41ecd37ca36ed2eb47512a6ab9"}, - {file = "coverage-7.4.4-cp312-cp312-win32.whl", hash = "sha256:137eb07173141545e07403cca94ab625cc1cc6bc4c1e97b6e3846270e7e1fea0"}, - {file = "coverage-7.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:d71eec7d83298f1af3326ce0ff1d0ea83c7cb98f72b577097f9083b20bdaf05e"}, - {file = "coverage-7.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ae728ff3b5401cc320d792866987e7e7e880e6ebd24433b70a33b643bb0384"}, - {file = "coverage-7.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc4f1358cb0c78edef3ed237ef2c86056206bb8d9140e73b6b89fbcfcbdd40e1"}, - {file = "coverage-7.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8130a2aa2acb8788e0b56938786c33c7c98562697bf9f4c7d6e8e5e3a0501e4a"}, - {file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf271892d13e43bc2b51e6908ec9a6a5094a4df1d8af0bfc360088ee6c684409"}, - {file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4cdc86d54b5da0df6d3d3a2f0b710949286094c3a6700c21e9015932b81447e"}, - {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ae71e7ddb7a413dd60052e90528f2f65270aad4b509563af6d03d53e979feafd"}, - {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:38dd60d7bf242c4ed5b38e094baf6401faa114fc09e9e6632374388a404f98e7"}, - {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa5b1c1bfc28384f1f53b69a023d789f72b2e0ab1b3787aae16992a7ca21056c"}, - {file = "coverage-7.4.4-cp38-cp38-win32.whl", hash = "sha256:dfa8fe35a0bb90382837b238fff375de15f0dcdb9ae68ff85f7a63649c98527e"}, - {file = "coverage-7.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:b2991665420a803495e0b90a79233c1433d6ed77ef282e8e152a324bbbc5e0c8"}, - {file = "coverage-7.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b799445b9f7ee8bf299cfaed6f5b226c0037b74886a4e11515e569b36fe310d"}, - {file = "coverage-7.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b4d33f418f46362995f1e9d4f3a35a1b6322cb959c31d88ae56b0298e1c22357"}, - {file = "coverage-7.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aadacf9a2f407a4688d700e4ebab33a7e2e408f2ca04dbf4aef17585389eff3e"}, - {file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c95949560050d04d46b919301826525597f07b33beba6187d04fa64d47ac82e"}, - {file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff7687ca3d7028d8a5f0ebae95a6e4827c5616b31a4ee1192bdfde697db110d4"}, - {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5fc1de20b2d4a061b3df27ab9b7c7111e9a710f10dc2b84d33a4ab25065994ec"}, - {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c74880fc64d4958159fbd537a091d2a585448a8f8508bf248d72112723974cbd"}, - {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:742a76a12aa45b44d236815d282b03cfb1de3b4323f3e4ec933acfae08e54ade"}, - {file = "coverage-7.4.4-cp39-cp39-win32.whl", hash = "sha256:d89d7b2974cae412400e88f35d86af72208e1ede1a541954af5d944a8ba46c57"}, - {file = "coverage-7.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:9ca28a302acb19b6af89e90f33ee3e1906961f94b54ea37de6737b7ca9d8827c"}, - {file = "coverage-7.4.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:b2c5edc4ac10a7ef6605a966c58929ec6c1bd0917fb8c15cb3363f65aa40e677"}, - {file = "coverage-7.4.4.tar.gz", hash = "sha256:c901df83d097649e257e803be22592aedfd5182f07b3cc87d640bbb9afd50f49"}, + {file = "coverage-7.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0884920835a033b78d1c73b6d3bbcda8161a900f38a488829a83982925f6c2e"}, + {file = "coverage-7.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:39afcd3d4339329c5f58de48a52f6e4e50f6578dd6099961cf22228feb25f38f"}, + {file = "coverage-7.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a7b0ceee8147444347da6a66be737c9d78f3353b0681715b668b72e79203e4a"}, + {file = "coverage-7.5.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a9ca3f2fae0088c3c71d743d85404cec8df9be818a005ea065495bedc33da35"}, + {file = "coverage-7.5.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd215c0c7d7aab005221608a3c2b46f58c0285a819565887ee0b718c052aa4e"}, + {file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4bf0655ab60d754491004a5efd7f9cccefcc1081a74c9ef2da4735d6ee4a6223"}, + {file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:61c4bf1ba021817de12b813338c9be9f0ad5b1e781b9b340a6d29fc13e7c1b5e"}, + {file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:db66fc317a046556a96b453a58eced5024af4582a8dbdc0c23ca4dbc0d5b3146"}, + {file = "coverage-7.5.1-cp310-cp310-win32.whl", hash = "sha256:b016ea6b959d3b9556cb401c55a37547135a587db0115635a443b2ce8f1c7228"}, + {file = "coverage-7.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:df4e745a81c110e7446b1cc8131bf986157770fa405fe90e15e850aaf7619bc8"}, + {file = "coverage-7.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:796a79f63eca8814ca3317a1ea443645c9ff0d18b188de470ed7ccd45ae79428"}, + {file = "coverage-7.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4fc84a37bfd98db31beae3c2748811a3fa72bf2007ff7902f68746d9757f3746"}, + {file = "coverage-7.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6175d1a0559986c6ee3f7fccfc4a90ecd12ba0a383dcc2da30c2b9918d67d8a3"}, + {file = "coverage-7.5.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fc81d5878cd6274ce971e0a3a18a8803c3fe25457165314271cf78e3aae3aa2"}, + {file = "coverage-7.5.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:556cf1a7cbc8028cb60e1ff0be806be2eded2daf8129b8811c63e2b9a6c43bca"}, + {file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9981706d300c18d8b220995ad22627647be11a4276721c10911e0e9fa44c83e8"}, + {file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d7fed867ee50edf1a0b4a11e8e5d0895150e572af1cd6d315d557758bfa9c057"}, + {file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ef48e2707fb320c8f139424a596f5b69955a85b178f15af261bab871873bb987"}, + {file = "coverage-7.5.1-cp311-cp311-win32.whl", hash = "sha256:9314d5678dcc665330df5b69c1e726a0e49b27df0461c08ca12674bcc19ef136"}, + {file = "coverage-7.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:5fa567e99765fe98f4e7d7394ce623e794d7cabb170f2ca2ac5a4174437e90dd"}, + {file = "coverage-7.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b6cf3764c030e5338e7f61f95bd21147963cf6aa16e09d2f74f1fa52013c1206"}, + {file = "coverage-7.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ec92012fefebee89a6b9c79bc39051a6cb3891d562b9270ab10ecfdadbc0c34"}, + {file = "coverage-7.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16db7f26000a07efcf6aea00316f6ac57e7d9a96501e990a36f40c965ec7a95d"}, + {file = "coverage-7.5.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:beccf7b8a10b09c4ae543582c1319c6df47d78fd732f854ac68d518ee1fb97fa"}, + {file = "coverage-7.5.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8748731ad392d736cc9ccac03c9845b13bb07d020a33423fa5b3a36521ac6e4e"}, + {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7352b9161b33fd0b643ccd1f21f3a3908daaddf414f1c6cb9d3a2fd618bf2572"}, + {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:7a588d39e0925f6a2bff87154752481273cdb1736270642aeb3635cb9b4cad07"}, + {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:68f962d9b72ce69ea8621f57551b2fa9c70509af757ee3b8105d4f51b92b41a7"}, + {file = "coverage-7.5.1-cp312-cp312-win32.whl", hash = "sha256:f152cbf5b88aaeb836127d920dd0f5e7edff5a66f10c079157306c4343d86c19"}, + {file = "coverage-7.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:5a5740d1fb60ddf268a3811bcd353de34eb56dc24e8f52a7f05ee513b2d4f596"}, + {file = "coverage-7.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e2213def81a50519d7cc56ed643c9e93e0247f5bbe0d1247d15fa520814a7cd7"}, + {file = "coverage-7.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5037f8fcc2a95b1f0e80585bd9d1ec31068a9bcb157d9750a172836e98bc7a90"}, + {file = "coverage-7.5.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3721c2c9e4c4953a41a26c14f4cef64330392a6d2d675c8b1db3b645e31f0e"}, + {file = "coverage-7.5.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca498687ca46a62ae590253fba634a1fe9836bc56f626852fb2720f334c9e4e5"}, + {file = "coverage-7.5.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cdcbc320b14c3e5877ee79e649677cb7d89ef588852e9583e6b24c2e5072661"}, + {file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:57e0204b5b745594e5bc14b9b50006da722827f0b8c776949f1135677e88d0b8"}, + {file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fe7502616b67b234482c3ce276ff26f39ffe88adca2acf0261df4b8454668b4"}, + {file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9e78295f4144f9dacfed4f92935fbe1780021247c2fabf73a819b17f0ccfff8d"}, + {file = "coverage-7.5.1-cp38-cp38-win32.whl", hash = "sha256:1434e088b41594baa71188a17533083eabf5609e8e72f16ce8c186001e6b8c41"}, + {file = "coverage-7.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:0646599e9b139988b63704d704af8e8df7fa4cbc4a1f33df69d97f36cb0a38de"}, + {file = "coverage-7.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4cc37def103a2725bc672f84bd939a6fe4522310503207aae4d56351644682f1"}, + {file = "coverage-7.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc0b4d8bfeabd25ea75e94632f5b6e047eef8adaed0c2161ada1e922e7f7cece"}, + {file = "coverage-7.5.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d0a0f5e06881ecedfe6f3dd2f56dcb057b6dbeb3327fd32d4b12854df36bf26"}, + {file = "coverage-7.5.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9735317685ba6ec7e3754798c8871c2f49aa5e687cc794a0b1d284b2389d1bd5"}, + {file = "coverage-7.5.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d21918e9ef11edf36764b93101e2ae8cc82aa5efdc7c5a4e9c6c35a48496d601"}, + {file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c3e757949f268364b96ca894b4c342b41dc6f8f8b66c37878aacef5930db61be"}, + {file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:79afb6197e2f7f60c4824dd4b2d4c2ec5801ceb6ba9ce5d2c3080e5660d51a4f"}, + {file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d1d0d98d95dd18fe29dc66808e1accf59f037d5716f86a501fc0256455219668"}, + {file = "coverage-7.5.1-cp39-cp39-win32.whl", hash = "sha256:1cc0fe9b0b3a8364093c53b0b4c0c2dd4bb23acbec4c9240b5f284095ccf7981"}, + {file = "coverage-7.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:dde0070c40ea8bb3641e811c1cfbf18e265d024deff6de52c5950677a8fb1e0f"}, + {file = "coverage-7.5.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:6537e7c10cc47c595828b8a8be04c72144725c383c4702703ff4e42e44577312"}, + {file = "coverage-7.5.1.tar.gz", hash = "sha256:54de9ef3a9da981f7af93eafde4ede199e0846cd819eb27c88e2b712aae9708c"}, ] [[package]] name = "cryptography" -version = "42.0.5" +version = "42.0.7" requires_python = ">=3.7" summary = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." groups = ["cryptography", "dev", "docs", "full", "jwt", "linting"] @@ -907,38 +909,38 @@ dependencies = [ "cffi>=1.12; platform_python_implementation != \"PyPy\"", ] files = [ - {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:a30596bae9403a342c978fb47d9b0ee277699fa53bbafad14706af51fe543d16"}, - {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b7ffe927ee6531c78f81aa17e684e2ff617daeba7f189f911065b2ea2d526dec"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2424ff4c4ac7f6b8177b53c17ed5d8fa74ae5955656867f5a8affaca36a27abb"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:329906dcc7b20ff3cad13c069a78124ed8247adcac44b10bea1130e36caae0b4"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:b03c2ae5d2f0fc05f9a2c0c997e1bc18c8229f392234e8a0194f202169ccd278"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8837fe1d6ac4a8052a9a8ddab256bc006242696f03368a4009be7ee3075cdb7"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:0270572b8bd2c833c3981724b8ee9747b3ec96f699a9665470018594301439ee"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:b8cac287fafc4ad485b8a9b67d0ee80c66bf3574f655d3b97ef2e1082360faf1"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:16a48c23a62a2f4a285699dba2e4ff2d1cff3115b9df052cdd976a18856d8e3d"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2bce03af1ce5a5567ab89bd90d11e7bbdff56b8af3acbbec1faded8f44cb06da"}, - {file = "cryptography-42.0.5-cp37-abi3-win32.whl", hash = "sha256:b6cd2203306b63e41acdf39aa93b86fb566049aeb6dc489b70e34bcd07adca74"}, - {file = "cryptography-42.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:98d8dc6d012b82287f2c3d26ce1d2dd130ec200c8679b6213b3c73c08b2b7940"}, - {file = "cryptography-42.0.5-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:5e6275c09d2badf57aea3afa80d975444f4be8d3bc58f7f80d2a484c6f9485c8"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4985a790f921508f36f81831817cbc03b102d643b5fcb81cd33df3fa291a1a1"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cde5f38e614f55e28d831754e8a3bacf9ace5d1566235e39d91b35502d6936e"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7367d7b2eca6513681127ebad53b2582911d1736dc2ffc19f2c3ae49997496bc"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cd2030f6650c089aeb304cf093f3244d34745ce0cfcc39f20c6fbfe030102e2a"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a2913c5375154b6ef2e91c10b5720ea6e21007412f6437504ffea2109b5a33d7"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:c41fb5e6a5fe9ebcd58ca3abfeb51dffb5d83d6775405305bfa8715b76521922"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3eaafe47ec0d0ffcc9349e1708be2aaea4c6dd4978d76bf6eb0cb2c13636c6fc"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1b95b98b0d2af784078fa69f637135e3c317091b615cd0905f8b8a087e86fa30"}, - {file = "cryptography-42.0.5-cp39-abi3-win32.whl", hash = "sha256:1f71c10d1e88467126f0efd484bd44bca5e14c664ec2ede64c32f20875c0d413"}, - {file = "cryptography-42.0.5-cp39-abi3-win_amd64.whl", hash = "sha256:a011a644f6d7d03736214d38832e030d8268bcff4a41f728e6030325fea3e400"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9481ffe3cf013b71b2428b905c4f7a9a4f76ec03065b05ff499bb5682a8d9ad8"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:ba334e6e4b1d92442b75ddacc615c5476d4ad55cc29b15d590cc6b86efa487e2"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ba3e4a42397c25b7ff88cdec6e2a16c2be18720f317506ee25210f6d31925f9c"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:111a0d8553afcf8eb02a4fea6ca4f59d48ddb34497aa8706a6cf536f1a5ec576"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cd65d75953847815962c84a4654a84850b2bb4aed3f26fadcc1c13892e1e29f6"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e807b3188f9eb0eaa7bbb579b462c5ace579f1cedb28107ce8b48a9f7ad3679e"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f12764b8fffc7a123f641d7d049d382b73f96a34117e0b637b80643169cec8ac"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:37dd623507659e08be98eec89323469e8c7b4c1407c85112634ae3dbdb926fdd"}, - {file = "cryptography-42.0.5.tar.gz", hash = "sha256:6fe07eec95dfd477eb9530aef5bead34fec819b3aaf6c5bd6d20565da607bfe1"}, + {file = "cryptography-42.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:a987f840718078212fdf4504d0fd4c6effe34a7e4740378e59d47696e8dfb477"}, + {file = "cryptography-42.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:bd13b5e9b543532453de08bcdc3cc7cebec6f9883e886fd20a92f26940fd3e7a"}, + {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a79165431551042cc9d1d90e6145d5d0d3ab0f2d66326c201d9b0e7f5bf43604"}, + {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a47787a5e3649008a1102d3df55424e86606c9bae6fb77ac59afe06d234605f8"}, + {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:02c0eee2d7133bdbbc5e24441258d5d2244beb31da5ed19fbb80315f4bbbff55"}, + {file = "cryptography-42.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:5e44507bf8d14b36b8389b226665d597bc0f18ea035d75b4e53c7b1ea84583cc"}, + {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7f8b25fa616d8b846aef64b15c606bb0828dbc35faf90566eb139aa9cff67af2"}, + {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:93a3209f6bb2b33e725ed08ee0991b92976dfdcf4e8b38646540674fc7508e13"}, + {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e6b8f1881dac458c34778d0a424ae5769de30544fc678eac51c1c8bb2183e9da"}, + {file = "cryptography-42.0.7-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3de9a45d3b2b7d8088c3fbf1ed4395dfeff79d07842217b38df14ef09ce1d8d7"}, + {file = "cryptography-42.0.7-cp37-abi3-win32.whl", hash = "sha256:789caea816c6704f63f6241a519bfa347f72fbd67ba28d04636b7c6b7da94b0b"}, + {file = "cryptography-42.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:8cb8ce7c3347fcf9446f201dc30e2d5a3c898d009126010cbd1f443f28b52678"}, + {file = "cryptography-42.0.7-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:a3a5ac8b56fe37f3125e5b72b61dcde43283e5370827f5233893d461b7360cd4"}, + {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:779245e13b9a6638df14641d029add5dc17edbef6ec915688f3acb9e720a5858"}, + {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d563795db98b4cd57742a78a288cdbdc9daedac29f2239793071fe114f13785"}, + {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:31adb7d06fe4383226c3e963471f6837742889b3c4caa55aac20ad951bc8ffda"}, + {file = "cryptography-42.0.7-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:efd0bf5205240182e0f13bcaea41be4fdf5c22c5129fc7ced4a0282ac86998c9"}, + {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a9bc127cdc4ecf87a5ea22a2556cab6c7eda2923f84e4f3cc588e8470ce4e42e"}, + {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:3577d029bc3f4827dd5bf8bf7710cac13527b470bbf1820a3f394adb38ed7d5f"}, + {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2e47577f9b18723fa294b0ea9a17d5e53a227867a0a4904a1a076d1646d45ca1"}, + {file = "cryptography-42.0.7-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1a58839984d9cb34c855197043eaae2c187d930ca6d644612843b4fe8513c886"}, + {file = "cryptography-42.0.7-cp39-abi3-win32.whl", hash = "sha256:e6b79d0adb01aae87e8a44c2b64bc3f3fe59515280e00fb6d57a7267a2583cda"}, + {file = "cryptography-42.0.7-cp39-abi3-win_amd64.whl", hash = "sha256:16268d46086bb8ad5bf0a2b5544d8a9ed87a0e33f5e77dd3c3301e63d941a83b"}, + {file = "cryptography-42.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2954fccea107026512b15afb4aa664a5640cd0af630e2ee3962f2602693f0c82"}, + {file = "cryptography-42.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:362e7197754c231797ec45ee081f3088a27a47c6c01eff2ac83f60f85a50fe60"}, + {file = "cryptography-42.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4f698edacf9c9e0371112792558d2f705b5645076cc0aaae02f816a0171770fd"}, + {file = "cryptography-42.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5482e789294854c28237bba77c4c83be698be740e31a3ae5e879ee5444166582"}, + {file = "cryptography-42.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e9b2a6309f14c0497f348d08a065d52f3020656f675819fc405fb63bbcd26562"}, + {file = "cryptography-42.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d8e3098721b84392ee45af2dd554c947c32cc52f862b6a3ae982dbb90f577f14"}, + {file = "cryptography-42.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c65f96dad14f8528a447414125e1fc8feb2ad5a272b8f68477abbcc1ea7d94b9"}, + {file = "cryptography-42.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:36017400817987670037fbb0324d71489b6ead6231c9604f8fc1f7d008087c68"}, + {file = "cryptography-42.0.7.tar.gz", hash = "sha256:ecbfbc00bf55888edda9868a4cf927205de8499e7fabe6c050322298382953f2"}, ] [[package]] @@ -954,7 +956,7 @@ files = [ [[package]] name = "daphne" -version = "4.1.0" +version = "4.1.2" requires_python = ">=3.8" summary = "Django ASGI (HTTP/WebSocket) server" groups = ["dev"] @@ -964,8 +966,8 @@ dependencies = [ "twisted[tls]>=22.4", ] files = [ - {file = "daphne-4.1.0-py3-none-any.whl", hash = "sha256:7228cd6a3ca5a9b11c9a1c1c0414dab1bfb4ddc55ff234b545db8d71f6c24938"}, - {file = "daphne-4.1.0.tar.gz", hash = "sha256:882fab39d0b90c6b2709b38116c95f660b6cf236600115dd7c13161fb98b3448"}, + {file = "daphne-4.1.2-py3-none-any.whl", hash = "sha256:618d1322bb4d875342b99dd2a10da2d9aae7ee3645f765965fdc1e658ea5290a"}, + {file = "daphne-4.1.2.tar.gz", hash = "sha256:fcbcace38eb86624ae247c7ffdc8ac12f155d7d19eafac4247381896d6f33761"}, ] [[package]] @@ -1096,14 +1098,14 @@ files = [ [[package]] name = "exceptiongroup" -version = "1.2.0" +version = "1.2.1" requires_python = ">=3.7" summary = "Backport of PEP 654 (exception groups)" groups = ["cli", "default", "dev", "docs", "full", "linting", "standard", "test"] marker = "python_version < \"3.11\"" files = [ - {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, - {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, + {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, + {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, ] [[package]] @@ -1119,17 +1121,16 @@ files = [ [[package]] name = "faker" -version = "24.8.0" +version = "25.1.0" requires_python = ">=3.8" summary = "Faker is a Python package that generates fake data for you." groups = ["default", "docs", "full"] dependencies = [ "python-dateutil>=2.4", - "typing-extensions>=3.10.0.1; python_version <= \"3.8\"", ] files = [ - {file = "Faker-24.8.0-py3-none-any.whl", hash = "sha256:2f70a7817b4147d67c544192e169c5653060fce8aef758db0ea8823d89caac94"}, - {file = "Faker-24.8.0.tar.gz", hash = "sha256:1a46466b22c6bf5925448f725f90c6e0d8bf085819906520ddaa15aec58a6df5"}, + {file = "Faker-25.1.0-py3-none-any.whl", hash = "sha256:24e28dce0b89683bb9e017e042b971c8c4909cff551b6d46f1e207674c7c2526"}, + {file = "Faker-25.1.0.tar.gz", hash = "sha256:2107618cf306bb188dcfea3e5cfd94aa92d65c7293a2437c1e96a99c83274755"}, ] [[package]] @@ -1159,24 +1160,24 @@ files = [ [[package]] name = "filelock" -version = "3.13.4" +version = "3.14.0" requires_python = ">=3.8" summary = "A platform independent file lock." groups = ["docs", "linting"] files = [ - {file = "filelock-3.13.4-py3-none-any.whl", hash = "sha256:404e5e9253aa60ad457cae1be07c0f0ca90a63931200a47d9b6a6af84fd7b45f"}, - {file = "filelock-3.13.4.tar.gz", hash = "sha256:d13f466618bfde72bd2c18255e269f72542c6e70e7bac83a0232d6b1cc5c8cf4"}, + {file = "filelock-3.14.0-py3-none-any.whl", hash = "sha256:43339835842f110ca7ae60f1e1c160714c5a6afd15a2873419ab185334975c0f"}, + {file = "filelock-3.14.0.tar.gz", hash = "sha256:6ea72da3be9b8c82afd3edcf99f2fffbb5076335a5ae4d03248bb5b6c3eae78a"}, ] [[package]] name = "fsspec" -version = "2024.3.1" +version = "2024.5.0" requires_python = ">=3.8" summary = "File-system specification" groups = ["dev"] files = [ - {file = "fsspec-2024.3.1-py3-none-any.whl", hash = "sha256:918d18d41bf73f0e2b261824baeb1b124bcf771767e3a26425cd7dec3332f512"}, - {file = "fsspec-2024.3.1.tar.gz", hash = "sha256:f39780e282d7d117ffb42bb96992f8a90795e4d0fb0f661a70ca39fe9c43ded9"}, + {file = "fsspec-2024.5.0-py3-none-any.whl", hash = "sha256:e0fdbc446d67e182f49a70b82cf7889028a63588fde6b222521f10937b2b670c"}, + {file = "fsspec-2024.5.0.tar.gz", hash = "sha256:1d021b0b0f933e3b3029ed808eb400c08ba101ca2de4b3483fbc9ca23fcee94a"}, ] [[package]] @@ -1529,7 +1530,7 @@ files = [ [[package]] name = "hypothesis" -version = "6.100.1" +version = "6.102.6" requires_python = ">=3.8" summary = "A library for property-based testing" groups = ["dev"] @@ -1539,30 +1540,30 @@ dependencies = [ "sortedcontainers<3.0.0,>=2.1.0", ] files = [ - {file = "hypothesis-6.100.1-py3-none-any.whl", hash = "sha256:3dacf6ec90e8d14aaee02cde081ac9a17d5b70105e45e6ac822db72052c0195b"}, - {file = "hypothesis-6.100.1.tar.gz", hash = "sha256:ebff09d7fa4f1fb6a855a812baf17e578b4481b7b70ec6d96496210d1a4c6c35"}, + {file = "hypothesis-6.102.6-py3-none-any.whl", hash = "sha256:ef281ba8b2626ebade9f463fbe8851ae6ff6ae4a8621a9e54c7c2477a97ccff0"}, + {file = "hypothesis-6.102.6.tar.gz", hash = "sha256:ef5655b4ca349082241ab55f899a34ea6d75cc336a7b07356680909059db1349"}, ] [[package]] name = "identify" -version = "2.5.35" +version = "2.5.36" requires_python = ">=3.8" summary = "File identification library for Python" groups = ["linting"] files = [ - {file = "identify-2.5.35-py2.py3-none-any.whl", hash = "sha256:c4de0081837b211594f8e877a6b4fad7ca32bbfc1a9307fdd61c28bfe923f13e"}, - {file = "identify-2.5.35.tar.gz", hash = "sha256:10a7ca245cfcd756a554a7288159f72ff105ad233c7c4b9c6f0f4d108f5f6791"}, + {file = "identify-2.5.36-py2.py3-none-any.whl", hash = "sha256:37d93f380f4de590500d9dba7db359d0d3da95ffe7f9de1753faa159e71e7dfa"}, + {file = "identify-2.5.36.tar.gz", hash = "sha256:e5e00f54165f9047fbebeb4a560f9acfb8af4c88232be60a488e9b68d122745d"}, ] [[package]] name = "idna" -version = "3.6" +version = "3.7" requires_python = ">=3.5" summary = "Internationalized Domain Names in Applications (IDNA)" groups = ["cli", "default", "dev", "docs", "full", "linting", "piccolo", "pydantic", "standard"] files = [ - {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, - {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] [[package]] @@ -1718,7 +1719,7 @@ files = [ [[package]] name = "jinja2" -version = "3.1.3" +version = "3.1.4" requires_python = ">=3.7" summary = "A very fast and expressive template engine." groups = ["docs", "full", "jinja", "piccolo", "standard"] @@ -1726,8 +1727,8 @@ dependencies = [ "MarkupSafe>=2.0", ] files = [ - {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, - {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, ] [[package]] @@ -1759,11 +1760,11 @@ files = [ [[package]] name = "litestar-sphinx-theme" -version = "0.3.0" +version = "0.3.1" requires_python = ">=3.8,<4.0" git = "https://github.com/litestar-org/litestar-sphinx-theme.git" ref = "v3" -revision = "c6d7ef86a1e49482b36d7bd38155bc7ef5d59f5a" +revision = "4799f935c3023afb222058cba8c849e8fa9ae3ba" summary = "A Sphinx theme for the Litestar organization" groups = ["docs"] dependencies = [ @@ -1792,7 +1793,7 @@ files = [ [[package]] name = "mako" -version = "1.3.2" +version = "1.3.5" requires_python = ">=3.8" summary = "A super-fast templating language that borrows the best ideas from the existing templating languages." groups = ["docs", "full", "mako", "sqlalchemy"] @@ -1800,8 +1801,8 @@ dependencies = [ "MarkupSafe>=0.9.2", ] files = [ - {file = "Mako-1.3.2-py3-none-any.whl", hash = "sha256:32a99d70754dfce237019d17ffe4a282d2d3351b9c476e90d8a60e63f133b80c"}, - {file = "Mako-1.3.2.tar.gz", hash = "sha256:2a0c8ad7f6274271b3bb7467dd37cf9cc6dab4bc19cb69a4ef10669402de698e"}, + {file = "Mako-1.3.5-py3-none-any.whl", hash = "sha256:260f1dbc3a519453a9c856dedfe4beb4e50bd5a26d96386cb6c80856556bb91a"}, + {file = "Mako-1.3.5.tar.gz", hash = "sha256:48dbc20568c1d276a2698b36d968fa76161bf127194907ea6fc594fa81f943bc"}, ] [[package]] @@ -1891,19 +1892,19 @@ files = [ [[package]] name = "minijinja" -version = "1.0.16" +version = "2.0.1" requires_python = ">=3.8" summary = "An experimental Python binding of the Rust MiniJinja template engine." groups = ["docs", "full", "minijinja"] files = [ - {file = "minijinja-1.0.16-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:b7c1640d23ad719da999ab434fd58e895125e067d310d512c9a45ec7e7f301dc"}, - {file = "minijinja-1.0.16-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f83ca4a37d73b57b87cee4d50f7ce2ade2d6c04fbede49faabc37f7244161bd6"}, - {file = "minijinja-1.0.16-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87c61c94c014c3a6d2ef83ee2f67229ee23b13b3586c8d4d3a7d090ba19f1b6c"}, - {file = "minijinja-1.0.16-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:beeb7fd7552e351b071b1d01fa80600a332b364c83f0f4a5c96b96d74d162434"}, - {file = "minijinja-1.0.16-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:38cc378dd256bd3766f1c83754c9a4407356b6d992defc725eab8a7c203e07b9"}, - {file = "minijinja-1.0.16-cp38-abi3-win32.whl", hash = "sha256:f7f0da6c0d2b78bce9aa0f2bbfebc0ece18cdf8dae9430b4001055b8eb77911c"}, - {file = "minijinja-1.0.16-cp38-abi3-win_amd64.whl", hash = "sha256:0a41541c28fd7ce64b38ddc60974930f81c163440c348393bd5143debeb309b3"}, - {file = "minijinja-1.0.16.tar.gz", hash = "sha256:57f6e98bc735794eb87648f251e68fbfd9cf50333f6a4053b398751483350826"}, + {file = "minijinja-2.0.1-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:063b291cb31f5c33eb77bb4cb457f67f14426ca1418232b8ae9f267155d330cc"}, + {file = "minijinja-2.0.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a4e9d639dd89ce7fef86e82147082ab3c248a36950fa3fbe793685ba322c1b7"}, + {file = "minijinja-2.0.1-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a20373af4ee5430356c196c7fe5f19e3261a4fa16c944542b4de7a2349bac7a6"}, + {file = "minijinja-2.0.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ade637bf4826258811a785ccc4e5d41cd2bdf4ec317b1ed3daa4dbbdd020f37d"}, + {file = "minijinja-2.0.1-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d5ec956d777e0fee8e214af48363334c04f098e986038a9e8cb92a0564f81943"}, + {file = "minijinja-2.0.1-cp38-abi3-win32.whl", hash = "sha256:039f4d1a1a73f90917cff1ed7c617eb56e2b2f91bbbdc551adaa448e1673e5c2"}, + {file = "minijinja-2.0.1-cp38-abi3-win_amd64.whl", hash = "sha256:dca5d7689905dce340e36e47348b505c788daf297253b85a1aff506ea63ad1b8"}, + {file = "minijinja-2.0.1.tar.gz", hash = "sha256:e774beffebfb8a1ad17e638ef70917cf5e94593f79acb8a8fff7d983169f3a4e"}, ] [[package]] @@ -2118,7 +2119,7 @@ files = [ [[package]] name = "mypy" -version = "1.9.0" +version = "1.10.0" requires_python = ">=3.8" summary = "Optional static typing for Python" groups = ["linting"] @@ -2128,33 +2129,33 @@ dependencies = [ "typing-extensions>=4.1.0", ] files = [ - {file = "mypy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8a67616990062232ee4c3952f41c779afac41405806042a8126fe96e098419f"}, - {file = "mypy-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d357423fa57a489e8c47b7c85dfb96698caba13d66e086b412298a1a0ea3b0ed"}, - {file = "mypy-1.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49c87c15aed320de9b438ae7b00c1ac91cd393c1b854c2ce538e2a72d55df150"}, - {file = "mypy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:48533cdd345c3c2e5ef48ba3b0d3880b257b423e7995dada04248725c6f77374"}, - {file = "mypy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:4d3dbd346cfec7cb98e6cbb6e0f3c23618af826316188d587d1c1bc34f0ede03"}, - {file = "mypy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:653265f9a2784db65bfca694d1edd23093ce49740b2244cde583aeb134c008f3"}, - {file = "mypy-1.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a3c007ff3ee90f69cf0a15cbcdf0995749569b86b6d2f327af01fd1b8aee9dc"}, - {file = "mypy-1.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2418488264eb41f69cc64a69a745fad4a8f86649af4b1041a4c64ee61fc61129"}, - {file = "mypy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:68edad3dc7d70f2f17ae4c6c1b9471a56138ca22722487eebacfd1eb5321d612"}, - {file = "mypy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:85ca5fcc24f0b4aeedc1d02f93707bccc04733f21d41c88334c5482219b1ccb3"}, - {file = "mypy-1.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aceb1db093b04db5cd390821464504111b8ec3e351eb85afd1433490163d60cd"}, - {file = "mypy-1.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0235391f1c6f6ce487b23b9dbd1327b4ec33bb93934aa986efe8a9563d9349e6"}, - {file = "mypy-1.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4d5ddc13421ba3e2e082a6c2d74c2ddb3979c39b582dacd53dd5d9431237185"}, - {file = "mypy-1.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:190da1ee69b427d7efa8aa0d5e5ccd67a4fb04038c380237a0d96829cb157913"}, - {file = "mypy-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:fe28657de3bfec596bbeef01cb219833ad9d38dd5393fc649f4b366840baefe6"}, - {file = "mypy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e54396d70be04b34f31d2edf3362c1edd023246c82f1730bbf8768c28db5361b"}, - {file = "mypy-1.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5e6061f44f2313b94f920e91b204ec600982961e07a17e0f6cd83371cb23f5c2"}, - {file = "mypy-1.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a10926e5473c5fc3da8abb04119a1f5811a236dc3a38d92015cb1e6ba4cb9e"}, - {file = "mypy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b685154e22e4e9199fc95f298661deea28aaede5ae16ccc8cbb1045e716b3e04"}, - {file = "mypy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:5d741d3fc7c4da608764073089e5f58ef6352bedc223ff58f2f038c2c4698a89"}, - {file = "mypy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:587ce887f75dd9700252a3abbc9c97bbe165a4a630597845c61279cf32dfbf02"}, - {file = "mypy-1.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f88566144752999351725ac623471661c9d1cd8caa0134ff98cceeea181789f4"}, - {file = "mypy-1.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61758fabd58ce4b0720ae1e2fea5cfd4431591d6d590b197775329264f86311d"}, - {file = "mypy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e49499be624dead83927e70c756970a0bc8240e9f769389cdf5714b0784ca6bf"}, - {file = "mypy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:571741dc4194b4f82d344b15e8837e8c5fcc462d66d076748142327626a1b6e9"}, - {file = "mypy-1.9.0-py3-none-any.whl", hash = "sha256:a260627a570559181a9ea5de61ac6297aa5af202f06fd7ab093ce74e7181e43e"}, - {file = "mypy-1.9.0.tar.gz", hash = "sha256:3cc5da0127e6a478cddd906068496a97a7618a21ce9b54bde5bf7e539c7af974"}, + {file = "mypy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da1cbf08fb3b851ab3b9523a884c232774008267b1f83371ace57f412fe308c2"}, + {file = "mypy-1.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:12b6bfc1b1a66095ab413160a6e520e1dc076a28f3e22f7fb25ba3b000b4ef99"}, + {file = "mypy-1.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e36fb078cce9904c7989b9693e41cb9711e0600139ce3970c6ef814b6ebc2b2"}, + {file = "mypy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2b0695d605ddcd3eb2f736cd8b4e388288c21e7de85001e9f85df9187f2b50f9"}, + {file = "mypy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:cd777b780312ddb135bceb9bc8722a73ec95e042f911cc279e2ec3c667076051"}, + {file = "mypy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3be66771aa5c97602f382230165b856c231d1277c511c9a8dd058be4784472e1"}, + {file = "mypy-1.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8b2cbaca148d0754a54d44121b5825ae71868c7592a53b7292eeb0f3fdae95ee"}, + {file = "mypy-1.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ec404a7cbe9fc0e92cb0e67f55ce0c025014e26d33e54d9e506a0f2d07fe5de"}, + {file = "mypy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e22e1527dc3d4aa94311d246b59e47f6455b8729f4968765ac1eacf9a4760bc7"}, + {file = "mypy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:a87dbfa85971e8d59c9cc1fcf534efe664d8949e4c0b6b44e8ca548e746a8d53"}, + {file = "mypy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a781f6ad4bab20eef8b65174a57e5203f4be627b46291f4589879bf4e257b97b"}, + {file = "mypy-1.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b808e12113505b97d9023b0b5e0c0705a90571c6feefc6f215c1df9381256e30"}, + {file = "mypy-1.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f55583b12156c399dce2df7d16f8a5095291354f1e839c252ec6c0611e86e2e"}, + {file = "mypy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cf18f9d0efa1b16478c4c129eabec36148032575391095f73cae2e722fcf9d5"}, + {file = "mypy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:bc6ac273b23c6b82da3bb25f4136c4fd42665f17f2cd850771cb600bdd2ebeda"}, + {file = "mypy-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9fd50226364cd2737351c79807775136b0abe084433b55b2e29181a4c3c878c0"}, + {file = "mypy-1.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f90cff89eea89273727d8783fef5d4a934be2fdca11b47def50cf5d311aff727"}, + {file = "mypy-1.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fcfc70599efde5c67862a07a1aaf50e55bce629ace26bb19dc17cece5dd31ca4"}, + {file = "mypy-1.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:075cbf81f3e134eadaf247de187bd604748171d6b79736fa9b6c9685b4083061"}, + {file = "mypy-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:3f298531bca95ff615b6e9f2fc0333aae27fa48052903a0ac90215021cdcfa4f"}, + {file = "mypy-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa7ef5244615a2523b56c034becde4e9e3f9b034854c93639adb667ec9ec2976"}, + {file = "mypy-1.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3236a4c8f535a0631f85f5fcdffba71c7feeef76a6002fcba7c1a8e57c8be1ec"}, + {file = "mypy-1.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a2b5cdbb5dd35aa08ea9114436e0d79aceb2f38e32c21684dcf8e24e1e92821"}, + {file = "mypy-1.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92f93b21c0fe73dc00abf91022234c79d793318b8a96faac147cd579c1671746"}, + {file = "mypy-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:28d0e038361b45f099cc086d9dd99c15ff14d0188f44ac883010e172ce86c38a"}, + {file = "mypy-1.10.0-py3-none-any.whl", hash = "sha256:f8c083976eb530019175aabadb60921e73b4f45736760826aa1689dda8208aee"}, + {file = "mypy-1.10.0.tar.gz", hash = "sha256:3d087fcbec056c4ee34974da493a826ce316947485cef3901f511848e687c131"}, ] [[package]] @@ -2318,7 +2319,7 @@ files = [ [[package]] name = "piccolo" -version = "1.5.0" +version = "1.5.1" requires_python = ">=3.8.0" summary = "A fast, user friendly ORM and query builder which supports asyncio." groups = ["docs", "full", "piccolo"] @@ -2332,8 +2333,8 @@ dependencies = [ "typing-extensions>=4.3.0", ] files = [ - {file = "piccolo-1.5.0-py3-none-any.whl", hash = "sha256:2cd5651e16cadaa8e599a5dc0b07426e6700fd44ce8fdd4f0afa78defde6a48b"}, - {file = "piccolo-1.5.0.tar.gz", hash = "sha256:dde6b96a2bb3fd90a416a0964ec940f22d0d08842ebfefb48585fca14093ede3"}, + {file = "piccolo-1.5.1-py3-none-any.whl", hash = "sha256:20a16ec2a24c4dae79b230acef0abac436733c4736fdcc0bc867697202adb3b9"}, + {file = "piccolo-1.5.1.tar.gz", hash = "sha256:fbaa253d3ce904a31e130198f8f7d5711b46ac67ff28ba2522e0c3d06d30d2dc"}, ] [[package]] @@ -2383,29 +2384,29 @@ files = [ [[package]] name = "platformdirs" -version = "4.2.0" +version = "4.2.1" requires_python = ">=3.8" -summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." groups = ["docs", "full", "linting", "piccolo"] files = [ - {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, - {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, + {file = "platformdirs-4.2.1-py3-none-any.whl", hash = "sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1"}, + {file = "platformdirs-4.2.1.tar.gz", hash = "sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf"}, ] [[package]] name = "pluggy" -version = "1.4.0" +version = "1.5.0" requires_python = ">=3.8" summary = "plugin and hook calling mechanisms for python" groups = ["test"] files = [ - {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, - {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [[package]] name = "polyfactory" -version = "2.15.0" +version = "2.16.0" requires_python = "<4.0,>=3.8" summary = "Mock data generation factories" groups = ["default", "docs", "full"] @@ -2414,8 +2415,8 @@ dependencies = [ "typing-extensions>=4.6.0", ] files = [ - {file = "polyfactory-2.15.0-py3-none-any.whl", hash = "sha256:ff5b6a8742cbd6fbde9f81310b9732d5421fbec31916d6ede5a977753110fbe9"}, - {file = "polyfactory-2.15.0.tar.gz", hash = "sha256:a3ff5263756ad74acf4001f04c1b6aab7d1197cbaa070352df79573a8dcd85ec"}, + {file = "polyfactory-2.16.0-py3-none-any.whl", hash = "sha256:168d8e50b77e91e35e691e8b3eedac43d7e423a6857fa26d473def96d53f0ecf"}, + {file = "polyfactory-2.16.0.tar.gz", hash = "sha256:03d8c706b70c4782ac8e637d0f6ab52760a7d11b712da5936a95a8f7022b2688"}, ] [[package]] @@ -2476,7 +2477,7 @@ files = [ [[package]] name = "psycopg" -version = "3.1.18" +version = "3.1.19" requires_python = ">=3.7" summary = "PostgreSQL database adapter for Python" groups = ["dev", "docs"] @@ -2486,78 +2487,76 @@ dependencies = [ "tzdata; sys_platform == \"win32\"", ] files = [ - {file = "psycopg-3.1.18-py3-none-any.whl", hash = "sha256:4d5a0a5a8590906daa58ebd5f3cfc34091377354a1acced269dd10faf55da60e"}, - {file = "psycopg-3.1.18.tar.gz", hash = "sha256:31144d3fb4c17d78094d9e579826f047d4af1da6a10427d91dfcfb6ecdf6f12b"}, + {file = "psycopg-3.1.19-py3-none-any.whl", hash = "sha256:dca5e5521c859f6606686432ae1c94e8766d29cc91f2ee595378c510cc5b0731"}, + {file = "psycopg-3.1.19.tar.gz", hash = "sha256:92d7b78ad82426cdcf1a0440678209faa890c6e1721361c2f8901f0dccd62961"}, ] [[package]] name = "psycopg-binary" -version = "3.1.18" +version = "3.1.19" requires_python = ">=3.7" summary = "PostgreSQL database adapter for Python -- C optimisation distribution" groups = ["dev"] marker = "implementation_name != \"pypy\"" files = [ - {file = "psycopg_binary-3.1.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c323103dfa663b88204cf5f028e83c77d7a715f9b6f51d2bbc8184b99ddd90a"}, - {file = "psycopg_binary-3.1.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:887f8d856c91510148be942c7acd702ccf761a05f59f8abc123c22ab77b5a16c"}, - {file = "psycopg_binary-3.1.18-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d322ba72cde4ca2eefc2196dad9ad7e52451acd2f04e3688d590290625d0c970"}, - {file = "psycopg_binary-3.1.18-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:489aa4fe5a0b653b68341e9e44af247dedbbc655326854aa34c163ef1bcb3143"}, - {file = "psycopg_binary-3.1.18-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55ff0948457bfa8c0d35c46e3a75193906d1c275538877ba65907fd67aa059ad"}, - {file = "psycopg_binary-3.1.18-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b15e3653c82384b043d820fc637199b5c6a36b37fa4a4943e0652785bb2bad5d"}, - {file = "psycopg_binary-3.1.18-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f8ff3bc08b43f36fdc24fedb86d42749298a458c4724fb588c4d76823ac39f54"}, - {file = "psycopg_binary-3.1.18-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:1729d0e3dfe2546d823841eb7a3d003144189d6f5e138ee63e5227f8b75276a5"}, - {file = "psycopg_binary-3.1.18-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:13bcd3742112446037d15e360b27a03af4b5afcf767f5ee374ef8f5dd7571b31"}, - {file = "psycopg_binary-3.1.18-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:320047e3d3554b857e16c2b6b615a85e0db6a02426f4d203a4594a2f125dfe57"}, - {file = "psycopg_binary-3.1.18-cp310-cp310-win_amd64.whl", hash = "sha256:888a72c2aca4316ca6d4a619291b805677bae99bba2f6e31a3c18424a48c7e4d"}, - {file = "psycopg_binary-3.1.18-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4e4de16a637ec190cbee82e0c2dc4860fed17a23a35f7a1e6dc479a5c6876722"}, - {file = "psycopg_binary-3.1.18-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6432047b8b24ef97e3fbee1d1593a0faaa9544c7a41a2c67d1f10e7621374c83"}, - {file = "psycopg_binary-3.1.18-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d684227ef8212e27da5f2aff9d4d303cc30b27ac1702d4f6881935549486dd5"}, - {file = "psycopg_binary-3.1.18-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:67284e2e450dc7a9e4d76e78c0bd357dc946334a3d410defaeb2635607f632cd"}, - {file = "psycopg_binary-3.1.18-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c9b6bd7fb5c6638cb32469674707649b526acfe786ba6d5a78ca4293d87bae4"}, - {file = "psycopg_binary-3.1.18-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7121acc783c4e86d2d320a7fb803460fab158a7f0a04c5e8c5d49065118c1e73"}, - {file = "psycopg_binary-3.1.18-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e28ff8f3de7b56588c2a398dc135fd9f157d12c612bd3daa7e6ba9872337f6f5"}, - {file = "psycopg_binary-3.1.18-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c84a0174109f329eeda169004c7b7ca2e884a6305acab4a39600be67f915ed38"}, - {file = "psycopg_binary-3.1.18-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:531381f6647fc267383dca88dbe8a70d0feff433a8e3d0c4939201fea7ae1b82"}, - {file = "psycopg_binary-3.1.18-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b293e01057e63c3ac0002aa132a1071ce0fdb13b9ee2b6b45d3abdb3525c597d"}, - {file = "psycopg_binary-3.1.18-cp311-cp311-win_amd64.whl", hash = "sha256:780a90bcb69bf27a8b08bc35b958e974cb6ea7a04cdec69e737f66378a344d68"}, - {file = "psycopg_binary-3.1.18-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:87dd9154b757a5fbf6d590f6f6ea75f4ad7b764a813ae04b1d91a70713f414a1"}, - {file = "psycopg_binary-3.1.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f876ebbf92db70125f6375f91ab4bc6b27648aa68f90d661b1fc5affb4c9731c"}, - {file = "psycopg_binary-3.1.18-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:258d2f0cb45e4574f8b2fe7c6d0a0e2eb58903a4fd1fbaf60954fba82d595ab7"}, - {file = "psycopg_binary-3.1.18-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd27f713f2e5ef3fd6796e66c1a5203a27a30ecb847be27a78e1df8a9a5ae68c"}, - {file = "psycopg_binary-3.1.18-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c38a4796abf7380f83b1653c2711cb2449dd0b2e5aca1caa75447d6fa5179c69"}, - {file = "psycopg_binary-3.1.18-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2f7f95746efd1be2dc240248cc157f4315db3fd09fef2adfcc2a76e24aa5741"}, - {file = "psycopg_binary-3.1.18-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4085f56a8d4fc8b455e8f44380705c7795be5317419aa5f8214f315e4205d804"}, - {file = "psycopg_binary-3.1.18-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2e2484ae835dedc80cdc7f1b1a939377dc967fed862262cfd097aa9f50cade46"}, - {file = "psycopg_binary-3.1.18-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:3c2b039ae0c45eee4cd85300ef802c0f97d0afc78350946a5d0ec77dd2d7e834"}, - {file = "psycopg_binary-3.1.18-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f54978c4b646dec77fefd8485fa82ec1a87807f334004372af1aaa6de9539a5"}, - {file = "psycopg_binary-3.1.18-cp312-cp312-win_amd64.whl", hash = "sha256:9ffcbbd389e486d3fd83d30107bbf8b27845a295051ccabde240f235d04ed921"}, - {file = "psycopg_binary-3.1.18-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:02bd4da45d5ee9941432e2e9bf36fa71a3ac21c6536fe7366d1bd3dd70d6b1e7"}, - {file = "psycopg_binary-3.1.18-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:39242546383f6b97032de7af30edb483d237a0616f6050512eee7b218a2aa8ee"}, - {file = "psycopg_binary-3.1.18-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d46ae44d66bf6058a812467f6ae84e4e157dee281bfb1cfaeca07dee07452e85"}, - {file = "psycopg_binary-3.1.18-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad35ac7fd989184bf4d38a87decfb5a262b419e8ba8dcaeec97848817412c64a"}, - {file = "psycopg_binary-3.1.18-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:247474af262bdd5559ee6e669926c4f23e9cf53dae2d34c4d991723c72196404"}, - {file = "psycopg_binary-3.1.18-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ebecbf2406cd6875bdd2453e31067d1bd8efe96705a9489ef37e93b50dc6f09"}, - {file = "psycopg_binary-3.1.18-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1859aeb2133f5ecdd9cbcee155f5e38699afc06a365f903b1512c765fd8d457e"}, - {file = "psycopg_binary-3.1.18-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:da917f6df8c6b2002043193cb0d74cc173b3af7eb5800ad69c4e1fbac2a71c30"}, - {file = "psycopg_binary-3.1.18-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:9e24e7b6a68a51cc3b162d0339ae4e1263b253e887987d5c759652f5692b5efe"}, - {file = "psycopg_binary-3.1.18-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e252d66276c992319ed6cd69a3ffa17538943954075051e992143ccbf6dc3d3e"}, - {file = "psycopg_binary-3.1.18-cp38-cp38-win_amd64.whl", hash = "sha256:5d6e860edf877d4413e4a807e837d55e3a7c7df701e9d6943c06e460fa6c058f"}, - {file = "psycopg_binary-3.1.18-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eea5f14933177ffe5c40b200f04f814258cc14b14a71024ad109f308e8bad414"}, - {file = "psycopg_binary-3.1.18-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:824a1bfd0db96cc6bef2d1e52d9e0963f5bf653dd5bc3ab519a38f5e6f21c299"}, - {file = "psycopg_binary-3.1.18-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a87e9eeb80ce8ec8c2783f29bce9a50bbcd2e2342a340f159c3326bf4697afa1"}, - {file = "psycopg_binary-3.1.18-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91074f78a9f890af5f2c786691575b6b93a4967ad6b8c5a90101f7b8c1a91d9c"}, - {file = "psycopg_binary-3.1.18-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e05f6825f8db4428782135e6986fec79b139210398f3710ed4aa6ef41473c008"}, - {file = "psycopg_binary-3.1.18-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f68ac2364a50d4cf9bb803b4341e83678668f1881a253e1224574921c69868c"}, - {file = "psycopg_binary-3.1.18-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7ac1785d67241d5074f8086705fa68e046becea27964267ab3abd392481d7773"}, - {file = "psycopg_binary-3.1.18-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:cd2a9f7f0d4dacc5b9ce7f0e767ae6cc64153264151f50698898c42cabffec0c"}, - {file = "psycopg_binary-3.1.18-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:3e4b0bb91da6f2238dbd4fbb4afc40dfb4f045bb611b92fce4d381b26413c686"}, - {file = "psycopg_binary-3.1.18-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:74e498586b72fb819ca8ea82107747d0cb6e00ae685ea6d1ab3f929318a8ce2d"}, - {file = "psycopg_binary-3.1.18-cp39-cp39-win_amd64.whl", hash = "sha256:d4422af5232699f14b7266a754da49dc9bcd45eba244cf3812307934cd5d6679"}, + {file = "psycopg_binary-3.1.19-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7204818f05151dd08f8f851defb01972ec9d2cc925608eb0de232563f203f354"}, + {file = "psycopg_binary-3.1.19-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d4e67fd86758dbeac85641419a54f84d74495a8683b58ad5dfad08b7fc37a8f"}, + {file = "psycopg_binary-3.1.19-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e12173e34b176e93ad2da913de30f774d5119c2d4d4640c6858d2d77dfa6c9bf"}, + {file = "psycopg_binary-3.1.19-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:052f5193304066318853b4b2e248f523c8f52b371fc4e95d4ef63baee3f30955"}, + {file = "psycopg_binary-3.1.19-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29008f3f8977f600b8a7fb07c2e041b01645b08121760609cc45e861a0364dc9"}, + {file = "psycopg_binary-3.1.19-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c6a9a651a08d876303ed059c9553df18b3c13c3406584a70a8f37f1a1fe2709"}, + {file = "psycopg_binary-3.1.19-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:91a645e6468c4f064b7f4f3b81074bdd68fe5aa2b8c5107de15dcd85ba6141be"}, + {file = "psycopg_binary-3.1.19-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5c6956808fd5cf0576de5a602243af8e04594b25b9a28675feddc71c5526410a"}, + {file = "psycopg_binary-3.1.19-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:1622ca27d5a7a98f7d8f35e8b146dc7efda4a4b6241d2edf7e076bd6bcecbeb4"}, + {file = "psycopg_binary-3.1.19-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a100482950a55228f648bd382bb71bfaff520002f29845274fccbbf02e28bd52"}, + {file = "psycopg_binary-3.1.19-cp310-cp310-win_amd64.whl", hash = "sha256:955ca8905c0251fc4af7ce0a20999e824a25652f53a558ab548b60969f1f368e"}, + {file = "psycopg_binary-3.1.19-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cf49e91dcf699b8a449944ed898ef1466b39b92720613838791a551bc8f587a"}, + {file = "psycopg_binary-3.1.19-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:964c307e400c5f33fa762ba1e19853e048814fcfbd9679cc923431adb7a2ead2"}, + {file = "psycopg_binary-3.1.19-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3433924e1b14074798331dc2bfae2af452ed7888067f2fc145835704d8981b15"}, + {file = "psycopg_binary-3.1.19-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00879d4c6be4b3afc510073f48a5e960f797200e261ab3d9bd9b7746a08c669d"}, + {file = "psycopg_binary-3.1.19-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34a6997c80f86d3dd80a4f078bb3b200079c47eeda4fd409d8899b883c90d2ac"}, + {file = "psycopg_binary-3.1.19-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0106e42b481677c41caa69474fe530f786dcef88b11b70000f0e45a03534bc8f"}, + {file = "psycopg_binary-3.1.19-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:81efe09ba27533e35709905c3061db4dc9fb814f637360578d065e2061fbb116"}, + {file = "psycopg_binary-3.1.19-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d312d6dddc18d9c164e1893706269c293cba1923118349d375962b1188dafb01"}, + {file = "psycopg_binary-3.1.19-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:bfd2c734da9950f7afaad5f132088e0e1478f32f042881fca6651bb0c8d14206"}, + {file = "psycopg_binary-3.1.19-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8a732610a5a6b4f06dadcf9288688a8ff202fd556d971436a123b7adb85596e2"}, + {file = "psycopg_binary-3.1.19-cp311-cp311-win_amd64.whl", hash = "sha256:321814a9a3ad785855a821b842aba08ca1b7de7dfb2979a2f0492dca9ec4ae70"}, + {file = "psycopg_binary-3.1.19-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4aa0ca13bb8a725bb6d12c13999217fd5bc8b86a12589f28a74b93e076fbb959"}, + {file = "psycopg_binary-3.1.19-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:469424e354ebcec949aa6aa30e5a9edc352a899d9a68ad7a48f97df83cc914cf"}, + {file = "psycopg_binary-3.1.19-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b04f5349313529ae1f1c42fe1aa0443faaf50fdf12d13866c2cc49683bfa53d0"}, + {file = "psycopg_binary-3.1.19-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:959feabddc7fffac89b054d6f23f3b3c62d7d3c90cd414a02e3747495597f150"}, + {file = "psycopg_binary-3.1.19-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e9da624a6ca4bc5f7fa1f03f8485446b5b81d5787b6beea2b4f8d9dbef878ad7"}, + {file = "psycopg_binary-3.1.19-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1823221a6b96e38b15686170d4fc5b36073efcb87cce7d3da660440b50077f6"}, + {file = "psycopg_binary-3.1.19-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:866db42f986298f0cf15d805225eb8df2228bf19f7997d7f1cb5f388cbfc6a0f"}, + {file = "psycopg_binary-3.1.19-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:738c34657305b5973af6dbb6711b07b179dfdd21196d60039ca30a74bafe9648"}, + {file = "psycopg_binary-3.1.19-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb9758473200384a04374d0e0cac6f451218ff6945a024f65a1526802c34e56e"}, + {file = "psycopg_binary-3.1.19-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0e991632777e217953ac960726158987da684086dd813ac85038c595e7382c91"}, + {file = "psycopg_binary-3.1.19-cp312-cp312-win_amd64.whl", hash = "sha256:1d87484dd42c8783c44a30400949efb3d81ef2487eaa7d64d1c54df90cf8b97a"}, + {file = "psycopg_binary-3.1.19-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d9b689c4a17dd3130791dcbb8c30dbf05602f7c2d56c792e193fb49adc7bf5f8"}, + {file = "psycopg_binary-3.1.19-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:017518bd2de4851adc826a224fb105411e148ad845e11355edd6786ba3dfedf5"}, + {file = "psycopg_binary-3.1.19-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c35fd811f339a3cbe7f9b54b2d9a5e592e57426c6cc1051632a62c59c4810208"}, + {file = "psycopg_binary-3.1.19-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38ed45ec9673709bfa5bc17f140e71dd4cca56d4e58ef7fd50d5a5043a4f55c6"}, + {file = "psycopg_binary-3.1.19-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:433f1c256108f9e26f480a8cd6ddb0fb37dbc87d7f5a97e4540a9da9b881f23f"}, + {file = "psycopg_binary-3.1.19-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ed61e43bf5dc8d0936daf03a19fef3168d64191dbe66483f7ad08c4cea0bc36b"}, + {file = "psycopg_binary-3.1.19-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4ae8109ff9fdf1fa0cb87ab6645298693fdd2666a7f5f85660df88f6965e0bb7"}, + {file = "psycopg_binary-3.1.19-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:a53809ee02e3952fae7977c19b30fd828bd117b8f5edf17a3a94212feb57faaf"}, + {file = "psycopg_binary-3.1.19-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9d39d5ffc151fb33bcd55b99b0e8957299c0b1b3e5a1a5f4399c1287ef0051a9"}, + {file = "psycopg_binary-3.1.19-cp38-cp38-win_amd64.whl", hash = "sha256:e14bc8250000921fcccd53722f86b3b3d1b57db901e206e49e2ab2afc5919c2d"}, + {file = "psycopg_binary-3.1.19-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cd88c5cea4efe614d5004fb5f5dcdea3d7d59422be796689e779e03363102d24"}, + {file = "psycopg_binary-3.1.19-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:621a814e60825162d38760c66351b4df679fd422c848b7c2f86ad399bff27145"}, + {file = "psycopg_binary-3.1.19-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46e50c05952b59a214e27d3606f6d510aaa429daed898e16b8a37bfbacc81acc"}, + {file = "psycopg_binary-3.1.19-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:03354a9db667c27946e70162cb0042c3929154167f3678a30d23cebfe0ad55b5"}, + {file = "psycopg_binary-3.1.19-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:703c2f3b79037581afec7baa2bdbcb0a1787f1758744a7662099b0eca2d721cb"}, + {file = "psycopg_binary-3.1.19-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6469ebd9e93327e9f5f36dcf8692fb1e7aeaf70087c1c15d4f2c020e0be3a891"}, + {file = "psycopg_binary-3.1.19-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:85bca9765c04b6be90cb46e7566ffe0faa2d7480ff5c8d5e055ac427f039fd24"}, + {file = "psycopg_binary-3.1.19-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:a836610d5c75e9cff98b9fdb3559c007c785c09eaa84a60d5d10ef6f85f671e8"}, + {file = "psycopg_binary-3.1.19-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ef8de7a1d9fb3518cc6b58e3c80b75a824209ad52b90c542686c912db8553dad"}, + {file = "psycopg_binary-3.1.19-cp39-cp39-win_amd64.whl", hash = "sha256:76fcd33342f38e35cd6b5408f1bc117d55ab8b16e5019d99b6d3ce0356c51717"}, ] [[package]] name = "psycopg-pool" -version = "3.2.1" +version = "3.2.2" requires_python = ">=3.8" summary = "Connection Pool for Psycopg" groups = ["dev"] @@ -2565,8 +2564,8 @@ dependencies = [ "typing-extensions>=4.4", ] files = [ - {file = "psycopg-pool-3.2.1.tar.gz", hash = "sha256:6509a75c073590952915eddbba7ce8b8332a440a31e77bba69561483492829ad"}, - {file = "psycopg_pool-3.2.1-py3-none-any.whl", hash = "sha256:060b551d1b97a8d358c668be58b637780b884de14d861f4f5ecc48b7563aafb7"}, + {file = "psycopg_pool-3.2.2-py3-none-any.whl", hash = "sha256:273081d0fbfaced4f35e69200c89cb8fbddfe277c38cc86c235b90a2ec2c8153"}, + {file = "psycopg_pool-3.2.2.tar.gz", hash = "sha256:9e22c370045f6d7f2666a5ad1b0caf345f9f1912195b0b25d0d3bcc4f3a7389c"}, ] [[package]] @@ -2641,19 +2640,19 @@ files = [ [[package]] name = "psycopg" -version = "3.1.18" +version = "3.1.19" extras = ["binary", "pool"] requires_python = ">=3.7" summary = "PostgreSQL database adapter for Python" groups = ["dev"] dependencies = [ - "psycopg-binary==3.1.18; implementation_name != \"pypy\"", + "psycopg-binary==3.1.19; implementation_name != \"pypy\"", "psycopg-pool", - "psycopg==3.1.18", + "psycopg==3.1.19", ] files = [ - {file = "psycopg-3.1.18-py3-none-any.whl", hash = "sha256:4d5a0a5a8590906daa58ebd5f3cfc34091377354a1acced269dd10faf55da60e"}, - {file = "psycopg-3.1.18.tar.gz", hash = "sha256:31144d3fb4c17d78094d9e579826f047d4af1da6a10427d91dfcfb6ecdf6f12b"}, + {file = "psycopg-3.1.19-py3-none-any.whl", hash = "sha256:dca5e5521c859f6606686432ae1c94e8766d29cc91f2ee595378c510cc5b0731"}, + {file = "psycopg-3.1.19.tar.gz", hash = "sha256:92d7b78ad82426cdcf1a0440678209faa890c6e1721361c2f8901f0dccd62961"}, ] [[package]] @@ -2695,114 +2694,114 @@ files = [ [[package]] name = "pydantic" -version = "2.6.4" +version = "2.7.1" requires_python = ">=3.8" summary = "Data validation using Python type hints" groups = ["dev", "docs", "full", "piccolo", "pydantic"] dependencies = [ "annotated-types>=0.4.0", - "pydantic-core==2.16.3", + "pydantic-core==2.18.2", "typing-extensions>=4.6.1", ] files = [ - {file = "pydantic-2.6.4-py3-none-any.whl", hash = "sha256:cc46fce86607580867bdc3361ad462bab9c222ef042d3da86f2fb333e1d916c5"}, - {file = "pydantic-2.6.4.tar.gz", hash = "sha256:b1704e0847db01817624a6b86766967f552dd9dbf3afba4004409f908dcc84e6"}, + {file = "pydantic-2.7.1-py3-none-any.whl", hash = "sha256:e029badca45266732a9a79898a15ae2e8b14840b1eabbb25844be28f0b33f3d5"}, + {file = "pydantic-2.7.1.tar.gz", hash = "sha256:e9dbb5eada8abe4d9ae5f46b9939aead650cd2b68f249bb3a8139dbe125803cc"}, ] [[package]] name = "pydantic-core" -version = "2.16.3" +version = "2.18.2" requires_python = ">=3.8" -summary = "" +summary = "Core functionality for Pydantic validation and serialization" groups = ["dev", "docs", "full", "piccolo", "pydantic"] dependencies = [ "typing-extensions!=4.7.0,>=4.6.0", ] files = [ - {file = "pydantic_core-2.16.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:75b81e678d1c1ede0785c7f46690621e4c6e63ccd9192af1f0bd9d504bbb6bf4"}, - {file = "pydantic_core-2.16.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9c865a7ee6f93783bd5d781af5a4c43dadc37053a5b42f7d18dc019f8c9d2bd1"}, - {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:162e498303d2b1c036b957a1278fa0899d02b2842f1ff901b6395104c5554a45"}, - {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2f583bd01bbfbff4eaee0868e6fc607efdfcc2b03c1c766b06a707abbc856187"}, - {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b926dd38db1519ed3043a4de50214e0d600d404099c3392f098a7f9d75029ff8"}, - {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:716b542728d4c742353448765aa7cdaa519a7b82f9564130e2b3f6766018c9ec"}, - {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc4ad7f7ee1a13d9cb49d8198cd7d7e3aa93e425f371a68235f784e99741561f"}, - {file = "pydantic_core-2.16.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bd87f48924f360e5d1c5f770d6155ce0e7d83f7b4e10c2f9ec001c73cf475c99"}, - {file = "pydantic_core-2.16.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0df446663464884297c793874573549229f9eca73b59360878f382a0fc085979"}, - {file = "pydantic_core-2.16.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4df8a199d9f6afc5ae9a65f8f95ee52cae389a8c6b20163762bde0426275b7db"}, - {file = "pydantic_core-2.16.3-cp310-none-win32.whl", hash = "sha256:456855f57b413f077dff513a5a28ed838dbbb15082ba00f80750377eed23d132"}, - {file = "pydantic_core-2.16.3-cp310-none-win_amd64.whl", hash = "sha256:732da3243e1b8d3eab8c6ae23ae6a58548849d2e4a4e03a1924c8ddf71a387cb"}, - {file = "pydantic_core-2.16.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:519ae0312616026bf4cedc0fe459e982734f3ca82ee8c7246c19b650b60a5ee4"}, - {file = "pydantic_core-2.16.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b3992a322a5617ded0a9f23fd06dbc1e4bd7cf39bc4ccf344b10f80af58beacd"}, - {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d62da299c6ecb04df729e4b5c52dc0d53f4f8430b4492b93aa8de1f541c4aac"}, - {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2acca2be4bb2f2147ada8cac612f8a98fc09f41c89f87add7256ad27332c2fda"}, - {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b662180108c55dfbf1280d865b2d116633d436cfc0bba82323554873967b340"}, - {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e7c6ed0dc9d8e65f24f5824291550139fe6f37fac03788d4580da0d33bc00c97"}, - {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1bb0827f56654b4437955555dc3aeeebeddc47c2d7ed575477f082622c49e"}, - {file = "pydantic_core-2.16.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e56f8186d6210ac7ece503193ec84104da7ceb98f68ce18c07282fcc2452e76f"}, - {file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:936e5db01dd49476fa8f4383c259b8b1303d5dd5fb34c97de194560698cc2c5e"}, - {file = "pydantic_core-2.16.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:33809aebac276089b78db106ee692bdc9044710e26f24a9a2eaa35a0f9fa70ba"}, - {file = "pydantic_core-2.16.3-cp311-none-win32.whl", hash = "sha256:ded1c35f15c9dea16ead9bffcde9bb5c7c031bff076355dc58dcb1cb436c4721"}, - {file = "pydantic_core-2.16.3-cp311-none-win_amd64.whl", hash = "sha256:d89ca19cdd0dd5f31606a9329e309d4fcbb3df860960acec32630297d61820df"}, - {file = "pydantic_core-2.16.3-cp311-none-win_arm64.whl", hash = "sha256:6162f8d2dc27ba21027f261e4fa26f8bcb3cf9784b7f9499466a311ac284b5b9"}, - {file = "pydantic_core-2.16.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0f56ae86b60ea987ae8bcd6654a887238fd53d1384f9b222ac457070b7ac4cff"}, - {file = "pydantic_core-2.16.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9bd22a2a639e26171068f8ebb5400ce2c1bc7d17959f60a3b753ae13c632975"}, - {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4204e773b4b408062960e65468d5346bdfe139247ee5f1ca2a378983e11388a2"}, - {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f651dd19363c632f4abe3480a7c87a9773be27cfe1341aef06e8759599454120"}, - {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aaf09e615a0bf98d406657e0008e4a8701b11481840be7d31755dc9f97c44053"}, - {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8e47755d8152c1ab5b55928ab422a76e2e7b22b5ed8e90a7d584268dd49e9c6b"}, - {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:500960cb3a0543a724a81ba859da816e8cf01b0e6aaeedf2c3775d12ee49cade"}, - {file = "pydantic_core-2.16.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cf6204fe865da605285c34cf1172879d0314ff267b1c35ff59de7154f35fdc2e"}, - {file = "pydantic_core-2.16.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d33dd21f572545649f90c38c227cc8631268ba25c460b5569abebdd0ec5974ca"}, - {file = "pydantic_core-2.16.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:49d5d58abd4b83fb8ce763be7794d09b2f50f10aa65c0f0c1696c677edeb7cbf"}, - {file = "pydantic_core-2.16.3-cp312-none-win32.whl", hash = "sha256:f53aace168a2a10582e570b7736cc5bef12cae9cf21775e3eafac597e8551fbe"}, - {file = "pydantic_core-2.16.3-cp312-none-win_amd64.whl", hash = "sha256:0d32576b1de5a30d9a97f300cc6a3f4694c428d956adbc7e6e2f9cad279e45ed"}, - {file = "pydantic_core-2.16.3-cp312-none-win_arm64.whl", hash = "sha256:ec08be75bb268473677edb83ba71e7e74b43c008e4a7b1907c6d57e940bf34b6"}, - {file = "pydantic_core-2.16.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:b1f6f5938d63c6139860f044e2538baeee6f0b251a1816e7adb6cbce106a1f01"}, - {file = "pydantic_core-2.16.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2a1ef6a36fdbf71538142ed604ad19b82f67b05749512e47f247a6ddd06afdc7"}, - {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:704d35ecc7e9c31d48926150afada60401c55efa3b46cd1ded5a01bdffaf1d48"}, - {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d937653a696465677ed583124b94a4b2d79f5e30b2c46115a68e482c6a591c8a"}, - {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9803edf8e29bd825f43481f19c37f50d2b01899448273b3a7758441b512acf8"}, - {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:72282ad4892a9fb2da25defeac8c2e84352c108705c972db82ab121d15f14e6d"}, - {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f752826b5b8361193df55afcdf8ca6a57d0232653494ba473630a83ba50d8c9"}, - {file = "pydantic_core-2.16.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4384a8f68ddb31a0b0c3deae88765f5868a1b9148939c3f4121233314ad5532c"}, - {file = "pydantic_core-2.16.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a4b2bf78342c40b3dc830880106f54328928ff03e357935ad26c7128bbd66ce8"}, - {file = "pydantic_core-2.16.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:13dcc4802961b5f843a9385fc821a0b0135e8c07fc3d9949fd49627c1a5e6ae5"}, - {file = "pydantic_core-2.16.3-cp38-none-win32.whl", hash = "sha256:e3e70c94a0c3841e6aa831edab1619ad5c511199be94d0c11ba75fe06efe107a"}, - {file = "pydantic_core-2.16.3-cp38-none-win_amd64.whl", hash = "sha256:ecdf6bf5f578615f2e985a5e1f6572e23aa632c4bd1dc67f8f406d445ac115ed"}, - {file = "pydantic_core-2.16.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:bda1ee3e08252b8d41fa5537413ffdddd58fa73107171a126d3b9ff001b9b820"}, - {file = "pydantic_core-2.16.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:21b888c973e4f26b7a96491c0965a8a312e13be108022ee510248fe379a5fa23"}, - {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be0ec334369316fa73448cc8c982c01e5d2a81c95969d58b8f6e272884df0074"}, - {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5b6079cc452a7c53dd378c6f881ac528246b3ac9aae0f8eef98498a75657805"}, - {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ee8d5f878dccb6d499ba4d30d757111847b6849ae07acdd1205fffa1fc1253c"}, - {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7233d65d9d651242a68801159763d09e9ec96e8a158dbf118dc090cd77a104c9"}, - {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6119dc90483a5cb50a1306adb8d52c66e447da88ea44f323e0ae1a5fcb14256"}, - {file = "pydantic_core-2.16.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:578114bc803a4c1ff9946d977c221e4376620a46cf78da267d946397dc9514a8"}, - {file = "pydantic_core-2.16.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d8f99b147ff3fcf6b3cc60cb0c39ea443884d5559a30b1481e92495f2310ff2b"}, - {file = "pydantic_core-2.16.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4ac6b4ce1e7283d715c4b729d8f9dab9627586dafce81d9eaa009dd7f25dd972"}, - {file = "pydantic_core-2.16.3-cp39-none-win32.whl", hash = "sha256:e7774b570e61cb998490c5235740d475413a1f6de823169b4cf94e2fe9e9f6b2"}, - {file = "pydantic_core-2.16.3-cp39-none-win_amd64.whl", hash = "sha256:9091632a25b8b87b9a605ec0e61f241c456e9248bfdcf7abdf344fdb169c81cf"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:36fa178aacbc277bc6b62a2c3da95226520da4f4e9e206fdf076484363895d2c"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:dcca5d2bf65c6fb591fff92da03f94cd4f315972f97c21975398bd4bd046854a"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a72fb9963cba4cd5793854fd12f4cfee731e86df140f59ff52a49b3552db241"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b60cc1a081f80a2105a59385b92d82278b15d80ebb3adb200542ae165cd7d183"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cbcc558401de90a746d02ef330c528f2e668c83350f045833543cd57ecead1ad"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:fee427241c2d9fb7192b658190f9f5fd6dfe41e02f3c1489d2ec1e6a5ab1e04a"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f4cb85f693044e0f71f394ff76c98ddc1bc0953e48c061725e540396d5c8a2e1"}, - {file = "pydantic_core-2.16.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b29eeb887aa931c2fcef5aa515d9d176d25006794610c264ddc114c053bf96fe"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a425479ee40ff021f8216c9d07a6a3b54b31c8267c6e17aa88b70d7ebd0e5e5b"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5c5cbc703168d1b7a838668998308018a2718c2130595e8e190220238addc96f"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99b6add4c0b39a513d323d3b93bc173dac663c27b99860dd5bf491b240d26137"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f76ee558751746d6a38f89d60b6228fa174e5172d143886af0f85aa306fd89"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:00ee1c97b5364b84cb0bd82e9bbf645d5e2871fb8c58059d158412fee2d33d8a"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:287073c66748f624be4cef893ef9174e3eb88fe0b8a78dc22e88eca4bc357ca6"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ed25e1835c00a332cb10c683cd39da96a719ab1dfc08427d476bce41b92531fc"}, - {file = "pydantic_core-2.16.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:86b3d0033580bd6bbe07590152007275bd7af95f98eaa5bd36f3da219dcd93da"}, - {file = "pydantic_core-2.16.3.tar.gz", hash = "sha256:1cac689f80a3abab2d3c0048b29eea5751114054f032a941a32de4c852c59cad"}, + {file = "pydantic_core-2.18.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9e08e867b306f525802df7cd16c44ff5ebbe747ff0ca6cf3fde7f36c05a59a81"}, + {file = "pydantic_core-2.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f0a21cbaa69900cbe1a2e7cad2aa74ac3cf21b10c3efb0fa0b80305274c0e8a2"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0680b1f1f11fda801397de52c36ce38ef1c1dc841a0927a94f226dea29c3ae3d"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:95b9d5e72481d3780ba3442eac863eae92ae43a5f3adb5b4d0a1de89d42bb250"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fcf5cd9c4b655ad666ca332b9a081112cd7a58a8b5a6ca7a3104bc950f2038"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b5155ff768083cb1d62f3e143b49a8a3432e6789a3abee8acd005c3c7af1c74"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:553ef617b6836fc7e4df130bb851e32fe357ce36336d897fd6646d6058d980af"}, + {file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b89ed9eb7d616ef5714e5590e6cf7f23b02d0d539767d33561e3675d6f9e3857"}, + {file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:75f7e9488238e920ab6204399ded280dc4c307d034f3924cd7f90a38b1829563"}, + {file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ef26c9e94a8c04a1b2924149a9cb081836913818e55681722d7f29af88fe7b38"}, + {file = "pydantic_core-2.18.2-cp310-none-win32.whl", hash = "sha256:182245ff6b0039e82b6bb585ed55a64d7c81c560715d1bad0cbad6dfa07b4027"}, + {file = "pydantic_core-2.18.2-cp310-none-win_amd64.whl", hash = "sha256:e23ec367a948b6d812301afc1b13f8094ab7b2c280af66ef450efc357d2ae543"}, + {file = "pydantic_core-2.18.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:219da3f096d50a157f33645a1cf31c0ad1fe829a92181dd1311022f986e5fbe3"}, + {file = "pydantic_core-2.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cc1cfd88a64e012b74e94cd00bbe0f9c6df57049c97f02bb07d39e9c852e19a4"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05b7133a6e6aeb8df37d6f413f7705a37ab4031597f64ab56384c94d98fa0e90"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:224c421235f6102e8737032483f43c1a8cfb1d2f45740c44166219599358c2cd"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b14d82cdb934e99dda6d9d60dc84a24379820176cc4a0d123f88df319ae9c150"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2728b01246a3bba6de144f9e3115b532ee44bd6cf39795194fb75491824a1413"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:470b94480bb5ee929f5acba6995251ada5e059a5ef3e0dfc63cca287283ebfa6"}, + {file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:997abc4df705d1295a42f95b4eec4950a37ad8ae46d913caeee117b6b198811c"}, + {file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75250dbc5290e3f1a0f4618db35e51a165186f9034eff158f3d490b3fed9f8a0"}, + {file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4456f2dca97c425231d7315737d45239b2b51a50dc2b6f0c2bb181fce6207664"}, + {file = "pydantic_core-2.18.2-cp311-none-win32.whl", hash = "sha256:269322dcc3d8bdb69f054681edff86276b2ff972447863cf34c8b860f5188e2e"}, + {file = "pydantic_core-2.18.2-cp311-none-win_amd64.whl", hash = "sha256:800d60565aec896f25bc3cfa56d2277d52d5182af08162f7954f938c06dc4ee3"}, + {file = "pydantic_core-2.18.2-cp311-none-win_arm64.whl", hash = "sha256:1404c69d6a676245199767ba4f633cce5f4ad4181f9d0ccb0577e1f66cf4c46d"}, + {file = "pydantic_core-2.18.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:fb2bd7be70c0fe4dfd32c951bc813d9fe6ebcbfdd15a07527796c8204bd36242"}, + {file = "pydantic_core-2.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6132dd3bd52838acddca05a72aafb6eab6536aa145e923bb50f45e78b7251043"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d904828195733c183d20a54230c0df0eb46ec746ea1a666730787353e87182"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c9bd70772c720142be1020eac55f8143a34ec9f82d75a8e7a07852023e46617f"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b8ed04b3582771764538f7ee7001b02e1170223cf9b75dff0bc698fadb00cf3"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6dac87ddb34aaec85f873d737e9d06a3555a1cc1a8e0c44b7f8d5daeb89d86f"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ca4ae5a27ad7a4ee5170aebce1574b375de390bc01284f87b18d43a3984df72"}, + {file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:886eec03591b7cf058467a70a87733b35f44707bd86cf64a615584fd72488b7c"}, + {file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ca7b0c1f1c983e064caa85f3792dd2fe3526b3505378874afa84baf662e12241"}, + {file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b4356d3538c3649337df4074e81b85f0616b79731fe22dd11b99499b2ebbdf3"}, + {file = "pydantic_core-2.18.2-cp312-none-win32.whl", hash = "sha256:8b172601454f2d7701121bbec3425dd71efcb787a027edf49724c9cefc14c038"}, + {file = "pydantic_core-2.18.2-cp312-none-win_amd64.whl", hash = "sha256:b1bd7e47b1558ea872bd16c8502c414f9e90dcf12f1395129d7bb42a09a95438"}, + {file = "pydantic_core-2.18.2-cp312-none-win_arm64.whl", hash = "sha256:98758d627ff397e752bc339272c14c98199c613f922d4a384ddc07526c86a2ec"}, + {file = "pydantic_core-2.18.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:9fdad8e35f278b2c3eb77cbdc5c0a49dada440657bf738d6905ce106dc1de439"}, + {file = "pydantic_core-2.18.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1d90c3265ae107f91a4f279f4d6f6f1d4907ac76c6868b27dc7fb33688cfb347"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:390193c770399861d8df9670fb0d1874f330c79caaca4642332df7c682bf6b91"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:82d5d4d78e4448683cb467897fe24e2b74bb7b973a541ea1dcfec1d3cbce39fb"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4774f3184d2ef3e14e8693194f661dea5a4d6ca4e3dc8e39786d33a94865cefd"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4d938ec0adf5167cb335acb25a4ee69a8107e4984f8fbd2e897021d9e4ca21b"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0e8b1be28239fc64a88a8189d1df7fad8be8c1ae47fcc33e43d4be15f99cc70"}, + {file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:868649da93e5a3d5eacc2b5b3b9235c98ccdbfd443832f31e075f54419e1b96b"}, + {file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:78363590ef93d5d226ba21a90a03ea89a20738ee5b7da83d771d283fd8a56761"}, + {file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:852e966fbd035a6468fc0a3496589b45e2208ec7ca95c26470a54daed82a0788"}, + {file = "pydantic_core-2.18.2-cp38-none-win32.whl", hash = "sha256:6a46e22a707e7ad4484ac9ee9f290f9d501df45954184e23fc29408dfad61350"}, + {file = "pydantic_core-2.18.2-cp38-none-win_amd64.whl", hash = "sha256:d91cb5ea8b11607cc757675051f61b3d93f15eca3cefb3e6c704a5d6e8440f4e"}, + {file = "pydantic_core-2.18.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:ae0a8a797a5e56c053610fa7be147993fe50960fa43609ff2a9552b0e07013e8"}, + {file = "pydantic_core-2.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:042473b6280246b1dbf530559246f6842b56119c2926d1e52b631bdc46075f2a"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a388a77e629b9ec814c1b1e6b3b595fe521d2cdc625fcca26fbc2d44c816804"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25add29b8f3b233ae90ccef2d902d0ae0432eb0d45370fe315d1a5cf231004b"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f459a5ce8434614dfd39bbebf1041952ae01da6bed9855008cb33b875cb024c0"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eff2de745698eb46eeb51193a9f41d67d834d50e424aef27df2fcdee1b153845"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8309f67285bdfe65c372ea3722b7a5642680f3dba538566340a9d36e920b5f0"}, + {file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f93a8a2e3938ff656a7c1bc57193b1319960ac015b6e87d76c76bf14fe0244b4"}, + {file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:22057013c8c1e272eb8d0eebc796701167d8377441ec894a8fed1af64a0bf399"}, + {file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cfeecd1ac6cc1fb2692c3d5110781c965aabd4ec5d32799773ca7b1456ac636b"}, + {file = "pydantic_core-2.18.2-cp39-none-win32.whl", hash = "sha256:0d69b4c2f6bb3e130dba60d34c0845ba31b69babdd3f78f7c0c8fae5021a253e"}, + {file = "pydantic_core-2.18.2-cp39-none-win_amd64.whl", hash = "sha256:d9319e499827271b09b4e411905b24a426b8fb69464dfa1696258f53a3334641"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a1874c6dd4113308bd0eb568418e6114b252afe44319ead2b4081e9b9521fe75"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:ccdd111c03bfd3666bd2472b674c6899550e09e9f298954cfc896ab92b5b0e6d"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e18609ceaa6eed63753037fc06ebb16041d17d28199ae5aba0052c51449650a9"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e5c584d357c4e2baf0ff7baf44f4994be121e16a2c88918a5817331fc7599d7"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43f0f463cf89ace478de71a318b1b4f05ebc456a9b9300d027b4b57c1a2064fb"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e1b395e58b10b73b07b7cf740d728dd4ff9365ac46c18751bf8b3d8cca8f625a"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0098300eebb1c837271d3d1a2cd2911e7c11b396eac9661655ee524a7f10587b"}, + {file = "pydantic_core-2.18.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:36789b70d613fbac0a25bb07ab3d9dba4d2e38af609c020cf4d888d165ee0bf3"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3f9a801e7c8f1ef8718da265bba008fa121243dfe37c1cea17840b0944dfd72c"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:3a6515ebc6e69d85502b4951d89131ca4e036078ea35533bb76327f8424531ce"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20aca1e2298c56ececfd8ed159ae4dde2df0781988c97ef77d5c16ff4bd5b400"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:223ee893d77a310a0391dca6df00f70bbc2f36a71a895cecd9a0e762dc37b349"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2334ce8c673ee93a1d6a65bd90327588387ba073c17e61bf19b4fd97d688d63c"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:cbca948f2d14b09d20268cda7b0367723d79063f26c4ffc523af9042cad95592"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b3ef08e20ec49e02d5c6717a91bb5af9b20f1805583cb0adfe9ba2c6b505b5ae"}, + {file = "pydantic_core-2.18.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6fdc8627910eed0c01aed6a390a252fe3ea6d472ee70fdde56273f198938374"}, + {file = "pydantic_core-2.18.2.tar.gz", hash = "sha256:2e29d20810dfc3043ee13ac7d9e25105799817683348823f305ab3f349b9386e"}, ] [[package]] name = "pydantic-extra-types" -version = "2.6.0" +version = "2.7.0" requires_python = ">=3.8" summary = "Extra Pydantic types." groups = ["docs", "full", "pydantic"] @@ -2810,40 +2809,40 @@ dependencies = [ "pydantic>=2.5.2", ] files = [ - {file = "pydantic_extra_types-2.6.0-py3-none-any.whl", hash = "sha256:d291d521c2e2bf2e6f11971caf8d639518124ae26a76d2e712599e98c4ef2b2b"}, - {file = "pydantic_extra_types-2.6.0.tar.gz", hash = "sha256:e9a93cfb245158462acb76621785219f80ad112303a0a7784d2ada65e6ed6cba"}, + {file = "pydantic_extra_types-2.7.0-py3-none-any.whl", hash = "sha256:ac01bbdaa4f85e4c4744a9792c5b0cfe61efa5028a0e670c3d8bfbf8b36c8543"}, + {file = "pydantic_extra_types-2.7.0.tar.gz", hash = "sha256:b9d9ddd755fa5960ec5a77cffcbd5d8796a0116e1dfc8f7c3a27fa0041693382"}, ] [[package]] name = "pydantic" -version = "2.6.4" +version = "2.7.1" extras = ["email"] requires_python = ">=3.8" summary = "Data validation using Python type hints" groups = ["docs", "full", "piccolo"] dependencies = [ "email-validator>=2.0.0", - "pydantic==2.6.4", + "pydantic==2.7.1", ] files = [ - {file = "pydantic-2.6.4-py3-none-any.whl", hash = "sha256:cc46fce86607580867bdc3361ad462bab9c222ef042d3da86f2fb333e1d916c5"}, - {file = "pydantic-2.6.4.tar.gz", hash = "sha256:b1704e0847db01817624a6b86766967f552dd9dbf3afba4004409f908dcc84e6"}, + {file = "pydantic-2.7.1-py3-none-any.whl", hash = "sha256:e029badca45266732a9a79898a15ae2e8b14840b1eabbb25844be28f0b33f3d5"}, + {file = "pydantic-2.7.1.tar.gz", hash = "sha256:e9dbb5eada8abe4d9ae5f46b9939aead650cd2b68f249bb3a8139dbe125803cc"}, ] [[package]] name = "pygments" -version = "2.17.2" -requires_python = ">=3.7" +version = "2.18.0" +requires_python = ">=3.8" summary = "Pygments is a syntax highlighting package written in Python." groups = ["default", "docs", "full"] files = [ - {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, - {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, ] [[package]] name = "pymongo" -version = "4.6.3" +version = "4.7.2" requires_python = ">=3.7" summary = "Python driver for MongoDB " groups = ["dev"] @@ -2851,71 +2850,56 @@ dependencies = [ "dnspython<3.0.0,>=1.16.0", ] files = [ - {file = "pymongo-4.6.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e344d0afdd7c06c1f1e66a4736593293f432defc2191e6b411fc9c82fa8c5adc"}, - {file = "pymongo-4.6.3-cp310-cp310-manylinux1_i686.whl", hash = "sha256:731a92dfc4022db763bfa835c6bd160f2d2cba6ada75749c2ed500e13983414b"}, - {file = "pymongo-4.6.3-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:c4726e36a2f7e92f09f5b8e92ba4db7525daffe31a0dcbcf0533edc0ade8c7d8"}, - {file = "pymongo-4.6.3-cp310-cp310-manylinux2014_i686.whl", hash = "sha256:00e6cfce111883ca63a3c12878286e0b89871f4b840290e61fb6f88ee0e687be"}, - {file = "pymongo-4.6.3-cp310-cp310-manylinux2014_ppc64le.whl", hash = "sha256:cc7a26edf79015c58eea46feb5b262cece55bc1d4929a8a9e0cbe7e6d6a9b0eb"}, - {file = "pymongo-4.6.3-cp310-cp310-manylinux2014_s390x.whl", hash = "sha256:4955be64d943b30f2a7ff98d818ca530f7cb37450bc6b32c37e0e74821907ef8"}, - {file = "pymongo-4.6.3-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:af039afc6d787502c02089759778b550cb2f25dbe2780f5b050a2e37031c3fbf"}, - {file = "pymongo-4.6.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccc15a7c7a99aed7d0831eaf78a607f1db0c7a255f96e3d18984231acd72f70c"}, - {file = "pymongo-4.6.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8e97c138d811e9367723fcd07c4402a9211caae20479fdd6301d57762778a69f"}, - {file = "pymongo-4.6.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ebcc145c74d06296ce0cad35992185064e5cb2aadef719586778c144f0cd4d37"}, - {file = "pymongo-4.6.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:664c64b6bdb31aceb80f0556951e5e2bf50d359270732268b4e7af00a1cf5d6c"}, - {file = "pymongo-4.6.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4056bc421d4df2c61db4e584415f2b0f1eebb92cbf9222f7f38303467c37117"}, - {file = "pymongo-4.6.3-cp310-cp310-win32.whl", hash = "sha256:cdbea2aac1a4caa66ee912af3601557d2bda2f9f69feec83601c78c7e53ece64"}, - {file = "pymongo-4.6.3-cp310-cp310-win_amd64.whl", hash = "sha256:6cec7279e5a1b74b257d0270a8c97943d745811066630a6bc6beb413c68c6a33"}, - {file = "pymongo-4.6.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:138b9fa18d40401c217bc038a48bcde4160b02d36d8632015b1804971a2eaa2f"}, - {file = "pymongo-4.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60931b0e07448afe8866ffff764cd5bf4b1a855dc84c7dcb3974c6aa6a377a59"}, - {file = "pymongo-4.6.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9b35f8bded43ff91475305445fedf0613f880ff7e25c75ae1028e1260a9b7a86"}, - {file = "pymongo-4.6.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:872bad5c83f7eec9da11e1fef5f858c6a4c79fe4a83c7780e7b0fe95d560ae3f"}, - {file = "pymongo-4.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2ad3e5bfcd345c0bfe9af69a82d720860b5b043c1657ffb513c18a0dee19c19"}, - {file = "pymongo-4.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e208f2ab7b495eff8fd175022abfb0abce6307ac5aee3f4de51fc1a459b71c9"}, - {file = "pymongo-4.6.3-cp311-cp311-win32.whl", hash = "sha256:4670edbb5ddd71a4d555668ef99b032a5f81b59e4145d66123aa0d831eac7883"}, - {file = "pymongo-4.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:1c2761302b6cbfd12e239ce1b8061d4cf424a361d199dcb32da534985cae9350"}, - {file = "pymongo-4.6.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:722f2b709b63311c0efda4fa4c603661faa4bec6bad24a6cc41a3bc6d841bf09"}, - {file = "pymongo-4.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:994386a4d6ad39e18bcede6dc8d1d693ec3ed897b88f86b1841fbc37227406da"}, - {file = "pymongo-4.6.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:391aea047bba928006114282f175bc8d09c53fe1b7d8920bf888325e229302fe"}, - {file = "pymongo-4.6.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4330c022024e7994b630199cdae909123e4b0e9cf15335de71b146c0f6a2435"}, - {file = "pymongo-4.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01277a7e183c59081368e4efbde2b8f577014431b257959ca98d3a4e8682dd51"}, - {file = "pymongo-4.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d30d5d7963453b478016bf7b0d87d7089ca24d93dbdecfbc9aa32f1b4772160a"}, - {file = "pymongo-4.6.3-cp312-cp312-win32.whl", hash = "sha256:a023804a3ac0f85d4510265b60978522368b5815772262e61e3a2222a8b315c9"}, - {file = "pymongo-4.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:2a6ae9a600bbc2dbff719c98bf5da584fb8a4f2bb23729a09be2e9c3dbc61c8a"}, - {file = "pymongo-4.6.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:4d167d546352869125dc86f6fda6dffc627d8a9c8963eaee665825f2520d542b"}, - {file = "pymongo-4.6.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:eaf3d594ebfd5e1f3503d81e06a5d78e33cda27418b36c2491c3d4ad4fca5972"}, - {file = "pymongo-4.6.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7ee79e02a7c5ed34706ecb5dad19e6c7d267cf86d28c075ef3127c58f3081279"}, - {file = "pymongo-4.6.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af5c5112db04cf62a5d9d224a24f289aaecb47d152c08a457cca81cee061d5bd"}, - {file = "pymongo-4.6.3-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:6b5aec78aa4840e8d6c3881900259892ab5733a366696ca10d99d68c3d73eaaf"}, - {file = "pymongo-4.6.3-cp38-cp38-manylinux2014_ppc64le.whl", hash = "sha256:9757602fb45c8ecc1883fe6db7c59c19d87eb3c645ec9342d28a6026837da931"}, - {file = "pymongo-4.6.3-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:dde9fb6e105ce054339256a8b7a9775212ebb29596ef4e402d7bbc63b354d202"}, - {file = "pymongo-4.6.3-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:7df8b166d3db6cfead4cf55b481408d8f0935d8bd8d6dbf64507c49ef82c7200"}, - {file = "pymongo-4.6.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53451190b8628e1ce7d1fe105dc376c3f10705127bd3b51fe3e107b9ff1851e6"}, - {file = "pymongo-4.6.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:75107a386d4ccf5291e75cce8ca3898430e7907f4cc1208a17c9efad33a1ea84"}, - {file = "pymongo-4.6.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4a0660ce32d8459b7f12dc3ca0141528fead62d3cce31b548f96f30902074cc0"}, - {file = "pymongo-4.6.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa310096450e9c461b7dfd66cbc1c41771fe36c06200440bb3e062b1d4a06b6e"}, - {file = "pymongo-4.6.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f465cca9b178e7bb782f952dd58e9e92f8ba056e585959465f2bb50feddef5f"}, - {file = "pymongo-4.6.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c67c19f653053ef2ebd7f1837c2978400058d6d7f66ec5760373a21eaf660158"}, - {file = "pymongo-4.6.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c701de8e483fb5e53874aab642235361aac6de698146b02c644389eaa8c137b6"}, - {file = "pymongo-4.6.3-cp38-cp38-win32.whl", hash = "sha256:90525454546536544307e6da9c81f331a71a1b144e2d038fec587cc9f9250285"}, - {file = "pymongo-4.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:3e1ba5a037c526a3f4060c28f8d45d71ed9626e2bf954b0cd9a8dcc3b45172ee"}, - {file = "pymongo-4.6.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:14a82593528cddc93cfea5ee78fac95ae763a3a4e124ca79ee0b24fbbc6da1c9"}, - {file = "pymongo-4.6.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:cd6c15242d9306ff1748681c3235284cbe9f807aeaa86cd17d85e72af626e9a7"}, - {file = "pymongo-4.6.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6de33f1b2eed91b802ec7abeb92ffb981d052f3604b45588309aae9e0f6e3c02"}, - {file = "pymongo-4.6.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:0182899aafe830f25cf96c5976d724efeaaf7b6646c15424ad8dd25422b2efe1"}, - {file = "pymongo-4.6.3-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:8d0ea740a2faa56f930dc82c5976d96c017ece26b29a1cddafb58721c7aab960"}, - {file = "pymongo-4.6.3-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:5c8a4982f5eb767c6fbfb8fb378683d09bcab7c3251ba64357eef600d43f6c23"}, - {file = "pymongo-4.6.3-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:becfa816545a48c8e740ac2fd624c1c121e1362072d68ffcf37a6b1be8ea187e"}, - {file = "pymongo-4.6.3-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:ff7d1f449fcad23d9bc8e8dc2b9972be38bcd76d99ea5f7d29b2efa929c2a7ff"}, - {file = "pymongo-4.6.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e097f877de4d6af13a33ef938bf2a2350f424be5deabf8b857da95f5b080487a"}, - {file = "pymongo-4.6.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:705a9bfd619301ee7e985d6f91f68b15dfcb2f6f36b8cc225cc82d4260d2bce5"}, - {file = "pymongo-4.6.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2ef1b4992ee1cb8bb16745e70afa0c02c5360220a7a8bb4775888721f052d0a6"}, - {file = "pymongo-4.6.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3d10bdd46cbc35a2109737d36ffbef32e7420569a87904738ad444ccb7ac2c5"}, - {file = "pymongo-4.6.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:17c1c143ba77d6e21fc8b48e93f0a5ed982a23447434e9ee4fbb6d633402506b"}, - {file = "pymongo-4.6.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9e51e30d67b468a2a634ade928b30cb3e420127f148a9aec60de33f39087bdc4"}, - {file = "pymongo-4.6.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:bec8e4e88984be157408f1923d25869e1b575c07711cdbdde596f66931800934"}, - {file = "pymongo-4.6.3-cp39-cp39-win32.whl", hash = "sha256:98877a9c4ad42df8253a12d8d17a3265781d1feb5c91c767bd153f88feb0b670"}, - {file = "pymongo-4.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:6d5b35da9e16cda630baed790ffc3d0d01029d269523a7cec34d2ec7e6823e75"}, - {file = "pymongo-4.6.3.tar.gz", hash = "sha256:400074090b9a631f120b42c61b222fd743490c133a5d2f99c0208cefcccc964e"}, + {file = "pymongo-4.7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:268d8578c0500012140c5460755ea405cbfe541ef47c81efa9d6744f0f99aeca"}, + {file = "pymongo-4.7.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:827611beb6c483260d520cfa6a49662d980dfa5368a04296f65fa39e78fccea7"}, + {file = "pymongo-4.7.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a754e366c404d19ff3f077ddeed64be31e0bb515e04f502bf11987f1baa55a16"}, + {file = "pymongo-4.7.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c44efab10d9a3db920530f7bcb26af8f408b7273d2f0214081d3891979726328"}, + {file = "pymongo-4.7.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35b3f0c7d49724859d4df5f0445818d525824a6cd55074c42573d9b50764df67"}, + {file = "pymongo-4.7.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e37faf298a37ffb3e0809e77fbbb0a32b6a2d18a83c59cfc2a7b794ea1136b0"}, + {file = "pymongo-4.7.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1bcd58669e56c08f1e72c5758868b5df169fe267501c949ee83c418e9df9155"}, + {file = "pymongo-4.7.2-cp310-cp310-win32.whl", hash = "sha256:c72d16fede22efe7cdd1f422e8da15760e9498024040429362886f946c10fe95"}, + {file = "pymongo-4.7.2-cp310-cp310-win_amd64.whl", hash = "sha256:12d1fef77d25640cb78893d07ff7d2fac4c4461d8eec45bd3b9ad491a1115d6e"}, + {file = "pymongo-4.7.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fc5af24fcf5fc6f7f40d65446400d45dd12bea933d0299dc9e90c5b22197f1e9"}, + {file = "pymongo-4.7.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:730778b6f0964b164c187289f906bbc84cb0524df285b7a85aa355bbec43eb21"}, + {file = "pymongo-4.7.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47a1a4832ef2f4346dcd1a10a36ade7367ad6905929ddb476459abb4fd1b98cb"}, + {file = "pymongo-4.7.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6eab12c6385526d386543d6823b07187fefba028f0da216506e00f0e1855119"}, + {file = "pymongo-4.7.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:37e9ea81fa59ee9274457ed7d59b6c27f6f2a5fe8e26f184ecf58ea52a019cb8"}, + {file = "pymongo-4.7.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e9d9d2c0aae73aa4369bd373ac2ac59f02c46d4e56c4b6d6e250cfe85f76802"}, + {file = "pymongo-4.7.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb6e00a79dff22c9a72212ad82021b54bdb3b85f38a85f4fc466bde581d7d17a"}, + {file = "pymongo-4.7.2-cp311-cp311-win32.whl", hash = "sha256:02efd1bb3397e24ef2af45923888b41a378ce00cb3a4259c5f4fc3c70497a22f"}, + {file = "pymongo-4.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:87bb453ac3eb44db95cb6d5a616fbc906c1c00661eec7f55696253a6245beb8a"}, + {file = "pymongo-4.7.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:12c466e02133b7f8f4ff1045c6b5916215c5f7923bc83fd6e28e290cba18f9f6"}, + {file = "pymongo-4.7.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f91073049c43d14e66696970dd708d319b86ee57ef9af359294eee072abaac79"}, + {file = "pymongo-4.7.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87032f818bf5052ab742812c715eff896621385c43f8f97cdd37d15b5d394e95"}, + {file = "pymongo-4.7.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6a87eef394039765679f75c6a47455a4030870341cb76eafc349c5944408c882"}, + {file = "pymongo-4.7.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d275596f840018858757561840767b39272ac96436fcb54f5cac6d245393fd97"}, + {file = "pymongo-4.7.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82102e353be13f1a6769660dd88115b1da382447672ba1c2662a0fbe3df1d861"}, + {file = "pymongo-4.7.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:194065c9d445017b3c82fb85f89aa2055464a080bde604010dc8eb932a6b3c95"}, + {file = "pymongo-4.7.2-cp312-cp312-win32.whl", hash = "sha256:db4380d1e69fdad1044a4b8f3bb105200542c49a0dde93452d938ff9db1d6d29"}, + {file = "pymongo-4.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:fadc6e8db7707c861ebe25b13ad6aca19ea4d2c56bf04a26691f46c23dadf6e4"}, + {file = "pymongo-4.7.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2dcf608d35644e8d276d61bf40a93339d8d66a0e5f3e3f75b2c155a421a1b71"}, + {file = "pymongo-4.7.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:25eeb2c18ede63891cbd617943dd9e6b9cbccc54f276e0b2e693a0cc40f243c5"}, + {file = "pymongo-4.7.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9349f0bb17a31371d4cacb64b306e4ca90413a3ad1fffe73ac7cd495570d94b5"}, + {file = "pymongo-4.7.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ffd4d7cb2e6c6e100e2b39606d38a9ffc934e18593dc9bb326196afc7d93ce3d"}, + {file = "pymongo-4.7.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9a8bd37f5dabc86efceb8d8cbff5969256523d42d08088f098753dba15f3b37a"}, + {file = "pymongo-4.7.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c78f156edc59b905c80c9003e022e1a764c54fd40ac4fea05b0764f829790e2"}, + {file = "pymongo-4.7.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9d892fb91e81cccb83f507cdb2ea0aa026ec3ced7f12a1d60f6a5bf0f20f9c1f"}, + {file = "pymongo-4.7.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:87832d6076c2c82f42870157414fd876facbb6554d2faf271ffe7f8f30ce7bed"}, + {file = "pymongo-4.7.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ce1a374ea0e49808e0380ffc64284c0ce0f12bd21042b4bef1af3eb7bdf49054"}, + {file = "pymongo-4.7.2-cp38-cp38-win32.whl", hash = "sha256:eb0642e5f0dd7e86bb358749cc278e70b911e617f519989d346f742dc9520dfb"}, + {file = "pymongo-4.7.2-cp38-cp38-win_amd64.whl", hash = "sha256:4bdb5ffe1cd3728c9479671a067ef44dacafc3743741d4dc700c377c4231356f"}, + {file = "pymongo-4.7.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:743552033c63f0afdb56b9189ab04b5c1dbffd7310cf7156ab98eebcecf24621"}, + {file = "pymongo-4.7.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5239776633f7578b81207e5646245415a5a95f6ae5ef5dff8e7c2357e6264bfc"}, + {file = "pymongo-4.7.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:727ad07952c155cd20045f2ce91143c7dc4fb01a5b4e8012905a89a7da554b0c"}, + {file = "pymongo-4.7.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9385654f01a90f73827af4db90c290a1519f7d9102ba43286e187b373e9a78e9"}, + {file = "pymongo-4.7.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d833651f1ba938bb7501f13e326b96cfbb7d98867b2d545ca6d69c7664903e0"}, + {file = "pymongo-4.7.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf17ea9cea14d59b0527403dd7106362917ced7c4ec936c4ba22bd36c912c8e0"}, + {file = "pymongo-4.7.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cecd2df037249d1c74f0af86fb5b766104a5012becac6ff63d85d1de53ba8b98"}, + {file = "pymongo-4.7.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65b4c00dedbd333698b83cd2095a639a6f0d7c4e2a617988f6c65fb46711f028"}, + {file = "pymongo-4.7.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d9b6cbc037108ff1a0a867e7670d8513c37f9bcd9ee3d2464411bfabf70ca002"}, + {file = "pymongo-4.7.2-cp39-cp39-win32.whl", hash = "sha256:cf28430ec1924af1bffed37b69a812339084697fd3f3e781074a0148e6475803"}, + {file = "pymongo-4.7.2-cp39-cp39-win_amd64.whl", hash = "sha256:e004527ea42a6b99a8b8d5b42b42762c3bdf80f88fbdb5c3a9d47f3808495b86"}, + {file = "pymongo-4.7.2.tar.gz", hash = "sha256:9024e1661c6e40acf468177bf90ce924d1bc681d2b244adda3ed7b2f4c4d17d7"}, ] [[package]] @@ -2967,7 +2951,7 @@ files = [ [[package]] name = "pytest-asyncio" -version = "0.23.6" +version = "0.23.7" requires_python = ">=3.8" summary = "Pytest support for asyncio" groups = ["test"] @@ -2975,8 +2959,8 @@ dependencies = [ "pytest<9,>=7.0.0", ] files = [ - {file = "pytest-asyncio-0.23.6.tar.gz", hash = "sha256:ffe523a89c1c222598c76856e76852b787504ddb72dd5d9b6617ffa8aa2cde5f"}, - {file = "pytest_asyncio-0.23.6-py3-none-any.whl", hash = "sha256:68516fdd1018ac57b846c9846b954f0393b26f094764a28c955eabb0536a4e8a"}, + {file = "pytest_asyncio-0.23.7-py3-none-any.whl", hash = "sha256:009b48127fbe44518a547bddd25611551b0e43ccdbf1e67d12479f569832c20b"}, + {file = "pytest_asyncio-0.23.7.tar.gz", hash = "sha256:5f5c72948f4c49e7db4f29f2521d4031f1c27f86e57b046126654083d4770268"}, ] [[package]] @@ -3053,17 +3037,17 @@ files = [ [[package]] name = "pytest-xdist" -version = "3.5.0" -requires_python = ">=3.7" +version = "3.6.1" +requires_python = ">=3.8" summary = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" groups = ["test"] dependencies = [ - "execnet>=1.1", - "pytest>=6.2.0", + "execnet>=2.1", + "pytest>=7.0.0", ] files = [ - {file = "pytest-xdist-3.5.0.tar.gz", hash = "sha256:cbb36f3d67e0c478baa57fa4edc8843887e0f6cfc42d677530a36d7472b32d8a"}, - {file = "pytest_xdist-3.5.0-py3-none-any.whl", hash = "sha256:d075629c7e00b611df89f490a5063944bee7a4362a5ff11c7cc7824a03dfce24"}, + {file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"}, + {file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"}, ] [[package]] @@ -3167,7 +3151,7 @@ files = [ [[package]] name = "redis" -version = "5.0.3" +version = "5.0.4" requires_python = ">=3.7" summary = "Python client for Redis database and key-value store" groups = ["docs", "full", "redis"] @@ -3175,24 +3159,112 @@ dependencies = [ "async-timeout>=4.0.3; python_full_version < \"3.11.3\"", ] files = [ - {file = "redis-5.0.3-py3-none-any.whl", hash = "sha256:5da9b8fe9e1254293756c16c008e8620b3d15fcc6dde6babde9541850e72a32d"}, - {file = "redis-5.0.3.tar.gz", hash = "sha256:4973bae7444c0fbed64a06b87446f79361cb7e4ec1538c022d696ed7a5015580"}, + {file = "redis-5.0.4-py3-none-any.whl", hash = "sha256:7adc2835c7a9b5033b7ad8f8918d09b7344188228809c98df07af226d39dec91"}, + {file = "redis-5.0.4.tar.gz", hash = "sha256:ec31f2ed9675cc54c21ba854cfe0462e6faf1d83c8ce5944709db8a4700b9c61"}, ] [[package]] name = "redis" -version = "5.0.3" +version = "5.0.4" extras = ["hiredis"] requires_python = ">=3.7" summary = "Python client for Redis database and key-value store" groups = ["docs", "full", "redis"] dependencies = [ "hiredis>=1.0.0", - "redis==5.0.3", + "redis==5.0.4", ] files = [ - {file = "redis-5.0.3-py3-none-any.whl", hash = "sha256:5da9b8fe9e1254293756c16c008e8620b3d15fcc6dde6babde9541850e72a32d"}, - {file = "redis-5.0.3.tar.gz", hash = "sha256:4973bae7444c0fbed64a06b87446f79361cb7e4ec1538c022d696ed7a5015580"}, + {file = "redis-5.0.4-py3-none-any.whl", hash = "sha256:7adc2835c7a9b5033b7ad8f8918d09b7344188228809c98df07af226d39dec91"}, + {file = "redis-5.0.4.tar.gz", hash = "sha256:ec31f2ed9675cc54c21ba854cfe0462e6faf1d83c8ce5944709db8a4700b9c61"}, +] + +[[package]] +name = "regex" +version = "2024.5.10" +requires_python = ">=3.8" +summary = "Alternative regular expression module, to replace re." +groups = ["linting"] +files = [ + {file = "regex-2024.5.10-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:eda3dd46df535da787ffb9036b5140f941ecb91701717df91c9daf64cabef953"}, + {file = "regex-2024.5.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1d5bd666466c8f00a06886ce1397ba8b12371c1f1c6d1bef11013e9e0a1464a8"}, + {file = "regex-2024.5.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:32e5f3b8e32918bfbdd12eca62e49ab3031125c454b507127ad6ecbd86e62fca"}, + {file = "regex-2024.5.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:534efd2653ebc4f26fc0e47234e53bf0cb4715bb61f98c64d2774a278b58c846"}, + {file = "regex-2024.5.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:193b7c6834a06f722f0ce1ba685efe80881de7c3de31415513862f601097648c"}, + {file = "regex-2024.5.10-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:160ba087232c5c6e2a1e7ad08bd3a3f49b58c815be0504d8c8aacfb064491cd8"}, + {file = "regex-2024.5.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:951be1eae7b47660412dc4938777a975ebc41936d64e28081bf2e584b47ec246"}, + {file = "regex-2024.5.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8a0f0ab5453e409586b11ebe91c672040bc804ca98d03a656825f7890cbdf88"}, + {file = "regex-2024.5.10-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9e6d4d6ae1827b2f8c7200aaf7501c37cf3f3896c86a6aaf2566448397c823dd"}, + {file = "regex-2024.5.10-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:161a206c8f3511e2f5fafc9142a2cc25d7fe9a1ec5ad9b4ad2496a7c33e1c5d2"}, + {file = "regex-2024.5.10-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:44b3267cea873684af022822195298501568ed44d542f9a2d9bebc0212e99069"}, + {file = "regex-2024.5.10-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:560278c9975694e1f0bc50da187abf2cdc1e4890739ea33df2bc4a85eeef143e"}, + {file = "regex-2024.5.10-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:70364a097437dd0a90b31cd77f09f7387ad9ac60ef57590971f43b7fca3082a5"}, + {file = "regex-2024.5.10-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:42be5de7cc8c1edac55db92d82b68dc8e683b204d6f5414c5a51997a323d7081"}, + {file = "regex-2024.5.10-cp310-cp310-win32.whl", hash = "sha256:9a8625849387b9d558d528e263ecc9c0fbde86cfa5c2f0eef43fff480ae24d71"}, + {file = "regex-2024.5.10-cp310-cp310-win_amd64.whl", hash = "sha256:903350bf44d7e4116b4d5898b30b15755d61dcd3161e3413a49c7db76f0bee5a"}, + {file = "regex-2024.5.10-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bf9596cba92ce7b1fd32c7b07c6e3212c7eed0edc271757e48bfcd2b54646452"}, + {file = "regex-2024.5.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:45cc13d398b6359a7708986386f72bd156ae781c3e83a68a6d4cee5af04b1ce9"}, + {file = "regex-2024.5.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ad45f3bccfcb00868f2871dce02a755529838d2b86163ab8a246115e80cfb7d6"}, + {file = "regex-2024.5.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33d19f0cde6838c81acffff25c7708e4adc7dd02896c9ec25c3939b1500a1778"}, + {file = "regex-2024.5.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0a9f89d7db5ef6bdf53e5cc8e6199a493d0f1374b3171796b464a74ebe8e508a"}, + {file = "regex-2024.5.10-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c6c71cf92b09e5faa72ea2c68aa1f61c9ce11cb66fdc5069d712f4392ddfd00"}, + {file = "regex-2024.5.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7467ad8b0eac0b28e52679e972b9b234b3de0ea5cee12eb50091d2b68145fe36"}, + {file = "regex-2024.5.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bc0db93ad039fc2fe32ccd3dd0e0e70c4f3d6e37ae83f0a487e1aba939bd2fbd"}, + {file = "regex-2024.5.10-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fa9335674d7c819674467c7b46154196c51efbaf5f5715187fd366814ba3fa39"}, + {file = "regex-2024.5.10-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7dda3091838206969c2b286f9832dff41e2da545b99d1cfaea9ebd8584d02708"}, + {file = "regex-2024.5.10-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:504b5116e2bd1821efd815941edff7535e93372a098e156bb9dffde30264e798"}, + {file = "regex-2024.5.10-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:91b53dea84415e8115506cc62e441a2b54537359c63d856d73cb1abe05af4c9a"}, + {file = "regex-2024.5.10-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1a3903128f9e17a500618e80c68165c78c741ebb17dd1a0b44575f92c3c68b02"}, + {file = "regex-2024.5.10-cp311-cp311-win32.whl", hash = "sha256:236cace6c1903effd647ed46ce6dd5d76d54985fc36dafc5256032886736c85d"}, + {file = "regex-2024.5.10-cp311-cp311-win_amd64.whl", hash = "sha256:12446827f43c7881decf2c126762e11425de5eb93b3b0d8b581344c16db7047a"}, + {file = "regex-2024.5.10-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:14905ed75c7a6edf423eb46c213ed3f4507c38115f1ed3c00f4ec9eafba50e58"}, + {file = "regex-2024.5.10-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4fad420b14ae1970a1f322e8ae84a1d9d89375eb71e1b504060ab2d1bfe68f3c"}, + {file = "regex-2024.5.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c46a76a599fcbf95f98755275c5527304cc4f1bb69919434c1e15544d7052910"}, + {file = "regex-2024.5.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0faecb6d5779753a6066a3c7a0471a8d29fe25d9981ca9e552d6d1b8f8b6a594"}, + {file = "regex-2024.5.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aab65121229c2ecdf4a31b793d99a6a0501225bd39b616e653c87b219ed34a49"}, + {file = "regex-2024.5.10-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:50e7e96a527488334379e05755b210b7da4a60fc5d6481938c1fa053e0c92184"}, + {file = "regex-2024.5.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba034c8db4b264ef1601eb33cd23d87c5013b8fb48b8161debe2e5d3bd9156b0"}, + {file = "regex-2024.5.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:031219782d97550c2098d9a68ce9e9eaefe67d2d81d8ff84c8354f9c009e720c"}, + {file = "regex-2024.5.10-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:62b5f7910b639f3c1d122d408421317c351e213ca39c964ad4121f27916631c6"}, + {file = "regex-2024.5.10-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:cd832bd9b6120d6074f39bdfbb3c80e416848b07ac72910f1c7f03131a6debc3"}, + {file = "regex-2024.5.10-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:e91b1976358e17197157b405cab408a5f4e33310cda211c49fc6da7cffd0b2f0"}, + {file = "regex-2024.5.10-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:571452362d552de508c37191b6abbbb660028b8b418e2d68c20779e0bc8eaaa8"}, + {file = "regex-2024.5.10-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5253dcb0bfda7214523de58b002eb0090cb530d7c55993ce5f6d17faf953ece7"}, + {file = "regex-2024.5.10-cp312-cp312-win32.whl", hash = "sha256:2f30a5ab8902f93930dc6f627c4dd5da2703333287081c85cace0fc6e21c25af"}, + {file = "regex-2024.5.10-cp312-cp312-win_amd64.whl", hash = "sha256:3799e36d60a35162bb35b2246d8bb012192b7437dff807ef79c14e7352706306"}, + {file = "regex-2024.5.10-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:bbdc5db2c98ac2bf1971ffa1410c87ca7a15800415f788971e8ba8520fc0fda9"}, + {file = "regex-2024.5.10-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6ccdeef4584450b6f0bddd5135354908dacad95425fcb629fe36d13e48b60f32"}, + {file = "regex-2024.5.10-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:29d839829209f3c53f004e1de8c3113efce6d98029f044fa5cfee666253ee7e6"}, + {file = "regex-2024.5.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0709ba544cf50bd5cb843df4b8bb6701bae2b70a8e88da9add8386cbca5c1385"}, + {file = "regex-2024.5.10-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:972b49f2fe1047b9249c958ec4fa1bdd2cf8ce305dc19d27546d5a38e57732d8"}, + {file = "regex-2024.5.10-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9cdbb1998da94607d5eec02566b9586f0e70d6438abf1b690261aac0edda7ab6"}, + {file = "regex-2024.5.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf7c8ee4861d9ef5b1120abb75846828c811f932d63311596ad25fa168053e00"}, + {file = "regex-2024.5.10-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d35d4cc9270944e95f9c88af757b0c9fc43f396917e143a5756608462c5223b"}, + {file = "regex-2024.5.10-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8722f72068b3e1156a4b2e1afde6810f1fc67155a9fa30a4b9d5b4bc46f18fb0"}, + {file = "regex-2024.5.10-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:696639a73ca78a380acfaa0a1f6dd8220616a99074c05bba9ba8bb916914b224"}, + {file = "regex-2024.5.10-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea057306ab469130167014b662643cfaed84651c792948891d003cf0039223a5"}, + {file = "regex-2024.5.10-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:b43b78f9386d3d932a6ce5af4b45f393d2e93693ee18dc4800d30a8909df700e"}, + {file = "regex-2024.5.10-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:c43395a3b7cc9862801a65c6994678484f186ce13c929abab44fb8a9e473a55a"}, + {file = "regex-2024.5.10-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0bc94873ba11e34837bffd7e5006703abeffc4514e2f482022f46ce05bd25e67"}, + {file = "regex-2024.5.10-cp38-cp38-win32.whl", hash = "sha256:1118ba9def608250250f4b3e3f48c62f4562ba16ca58ede491b6e7554bfa09ff"}, + {file = "regex-2024.5.10-cp38-cp38-win_amd64.whl", hash = "sha256:458d68d34fb74b906709735c927c029e62f7d06437a98af1b5b6258025223210"}, + {file = "regex-2024.5.10-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:15e593386ec6331e0ab4ac0795b7593f02ab2f4b30a698beb89fbdc34f92386a"}, + {file = "regex-2024.5.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ca23b41355ba95929e9505ee04e55495726aa2282003ed9b012d86f857d3e49b"}, + {file = "regex-2024.5.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2c8982ee19ccecabbaeac1ba687bfef085a6352a8c64f821ce2f43e6d76a9298"}, + {file = "regex-2024.5.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7117cb7d6ac7f2e985f3d18aa8a1728864097da1a677ffa69e970ca215baebf1"}, + {file = "regex-2024.5.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b66421f8878a0c82fc0c272a43e2121c8d4c67cb37429b764f0d5ad70b82993b"}, + {file = "regex-2024.5.10-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:224a9269f133564109ce668213ef3cb32bc72ccf040b0b51c72a50e569e9dc9e"}, + {file = "regex-2024.5.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab98016541543692a37905871a5ffca59b16e08aacc3d7d10a27297b443f572d"}, + {file = "regex-2024.5.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:51d27844763c273a122e08a3e86e7aefa54ee09fb672d96a645ece0454d8425e"}, + {file = "regex-2024.5.10-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:853cc36e756ff673bf984e9044ccc8fad60b95a748915dddeab9488aea974c73"}, + {file = "regex-2024.5.10-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4e7eaf9df15423d07b6050fb91f86c66307171b95ea53e2d87a7993b6d02c7f7"}, + {file = "regex-2024.5.10-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:169fd0acd7a259f58f417e492e93d0e15fc87592cd1e971c8c533ad5703b5830"}, + {file = "regex-2024.5.10-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:334b79ce9c08f26b4659a53f42892793948a613c46f1b583e985fd5a6bf1c149"}, + {file = "regex-2024.5.10-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:f03b1dbd4d9596dd84955bb40f7d885204d6aac0d56a919bb1e0ff2fb7e1735a"}, + {file = "regex-2024.5.10-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cfa6d61a76c77610ba9274c1a90a453062bdf6887858afbe214d18ad41cf6bde"}, + {file = "regex-2024.5.10-cp39-cp39-win32.whl", hash = "sha256:249fbcee0a277c32a3ce36d8e36d50c27c968fdf969e0fbe342658d4e010fbc8"}, + {file = "regex-2024.5.10-cp39-cp39-win_amd64.whl", hash = "sha256:0ce56a923f4c01d7568811bfdffe156268c0a7aae8a94c902b92fe34c4bde785"}, + {file = "regex-2024.5.10.tar.gz", hash = "sha256:304e7e2418146ae4d0ef0e9ffa28f881f7874b45b4994cc2279b21b6e7ae50c8"}, ] [[package]] @@ -3270,18 +3342,18 @@ files = [ [[package]] name = "rich-click" -version = "1.7.4" +version = "1.8.2" requires_python = ">=3.7" summary = "Format click help output nicely with rich" groups = ["default", "docs", "full"] dependencies = [ "click>=7", - "rich>=10.7.0", + "rich>=10.7", "typing-extensions", ] files = [ - {file = "rich-click-1.7.4.tar.gz", hash = "sha256:7ce5de8e4dc0333aec946113529b3eeb349f2e5d2fafee96b9edf8ee36a01395"}, - {file = "rich_click-1.7.4-py3-none-any.whl", hash = "sha256:e363655475c60fec5a3e16a1eb618118ed79e666c365a36006b107c17c93ac4e"}, + {file = "rich_click-1.8.2-py3-none-any.whl", hash = "sha256:b57856f304e4fe0394b82d7ce0784450758f8c8b4e201ccc4320501cc201806b"}, + {file = "rich_click-1.8.2.tar.gz", hash = "sha256:8e29bdede858b59aa2859a1ab1c4ccbd39ed7ed5870262dae756fba6b5dc72e8"}, ] [[package]] @@ -3365,28 +3437,28 @@ files = [ [[package]] name = "ruff" -version = "0.3.5" +version = "0.4.5" requires_python = ">=3.7" summary = "An extremely fast Python linter and code formatter, written in Rust." groups = ["docs", "linting"] files = [ - {file = "ruff-0.3.5-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:aef5bd3b89e657007e1be6b16553c8813b221ff6d92c7526b7e0227450981eac"}, - {file = "ruff-0.3.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:89b1e92b3bd9fca249153a97d23f29bed3992cff414b222fcd361d763fc53f12"}, - {file = "ruff-0.3.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e55771559c89272c3ebab23326dc23e7f813e492052391fe7950c1a5a139d89"}, - {file = "ruff-0.3.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dabc62195bf54b8a7876add6e789caae0268f34582333cda340497c886111c39"}, - {file = "ruff-0.3.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a05f3793ba25f194f395578579c546ca5d83e0195f992edc32e5907d142bfa3"}, - {file = "ruff-0.3.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:dfd3504e881082959b4160ab02f7a205f0fadc0a9619cc481982b6837b2fd4c0"}, - {file = "ruff-0.3.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87258e0d4b04046cf1d6cc1c56fadbf7a880cc3de1f7294938e923234cf9e498"}, - {file = "ruff-0.3.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:712e71283fc7d9f95047ed5f793bc019b0b0a29849b14664a60fd66c23b96da1"}, - {file = "ruff-0.3.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a532a90b4a18d3f722c124c513ffb5e5eaff0cc4f6d3aa4bda38e691b8600c9f"}, - {file = "ruff-0.3.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:122de171a147c76ada00f76df533b54676f6e321e61bd8656ae54be326c10296"}, - {file = "ruff-0.3.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d80a6b18a6c3b6ed25b71b05eba183f37d9bc8b16ace9e3d700997f00b74660b"}, - {file = "ruff-0.3.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a7b6e63194c68bca8e71f81de30cfa6f58ff70393cf45aab4c20f158227d5936"}, - {file = "ruff-0.3.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a759d33a20c72f2dfa54dae6e85e1225b8e302e8ac655773aff22e542a300985"}, - {file = "ruff-0.3.5-py3-none-win32.whl", hash = "sha256:9d8605aa990045517c911726d21293ef4baa64f87265896e491a05461cae078d"}, - {file = "ruff-0.3.5-py3-none-win_amd64.whl", hash = "sha256:dc56bb16a63c1303bd47563c60482a1512721053d93231cf7e9e1c6954395a0e"}, - {file = "ruff-0.3.5-py3-none-win_arm64.whl", hash = "sha256:faeeae9905446b975dcf6d4499dc93439b131f1443ee264055c5716dd947af55"}, - {file = "ruff-0.3.5.tar.gz", hash = "sha256:a067daaeb1dc2baf9b82a32dae67d154d95212080c80435eb052d95da647763d"}, + {file = "ruff-0.4.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8f58e615dec58b1a6b291769b559e12fdffb53cc4187160a2fc83250eaf54e96"}, + {file = "ruff-0.4.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:84dd157474e16e3a82745d2afa1016c17d27cb5d52b12e3d45d418bcc6d49264"}, + {file = "ruff-0.4.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25f483ad9d50b00e7fd577f6d0305aa18494c6af139bce7319c68a17180087f4"}, + {file = "ruff-0.4.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:63fde3bf6f3ad4e990357af1d30e8ba2730860a954ea9282c95fc0846f5f64af"}, + {file = "ruff-0.4.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78e3ba4620dee27f76bbcad97067766026c918ba0f2d035c2fc25cbdd04d9c97"}, + {file = "ruff-0.4.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:441dab55c568e38d02bbda68a926a3d0b54f5510095c9de7f95e47a39e0168aa"}, + {file = "ruff-0.4.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1169e47e9c4136c997f08f9857ae889d614c5035d87d38fda9b44b4338909cdf"}, + {file = "ruff-0.4.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:755ac9ac2598a941512fc36a9070a13c88d72ff874a9781493eb237ab02d75df"}, + {file = "ruff-0.4.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4b02a65985be2b34b170025a8b92449088ce61e33e69956ce4d316c0fe7cce0"}, + {file = "ruff-0.4.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:75a426506a183d9201e7e5664de3f6b414ad3850d7625764106f7b6d0486f0a1"}, + {file = "ruff-0.4.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:6e1b139b45e2911419044237d90b60e472f57285950e1492c757dfc88259bb06"}, + {file = "ruff-0.4.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a6f29a8221d2e3d85ff0c7b4371c0e37b39c87732c969b4d90f3dad2e721c5b1"}, + {file = "ruff-0.4.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d6ef817124d72b54cc923f3444828ba24fa45c3164bc9e8f1813db2f3d3a8a11"}, + {file = "ruff-0.4.5-py3-none-win32.whl", hash = "sha256:aed8166c18b1a169a5d3ec28a49b43340949e400665555b51ee06f22813ef062"}, + {file = "ruff-0.4.5-py3-none-win_amd64.whl", hash = "sha256:b0b03c619d2b4350b4a27e34fd2ac64d0dabe1afbf43de57d0f9d8a05ecffa45"}, + {file = "ruff-0.4.5-py3-none-win_arm64.whl", hash = "sha256:9d15de3425f53161b3f5a5658d4522e4eee5ea002bf2ac7aa380743dd9ad5fba"}, + {file = "ruff-0.4.5.tar.gz", hash = "sha256:286eabd47e7d4d521d199cab84deca135557e6d1e0f0d01c29e757c3cb151b54"}, ] [[package]] @@ -3408,18 +3480,18 @@ files = [ [[package]] name = "setuptools" -version = "69.2.0" +version = "69.5.1" requires_python = ">=3.8" summary = "Easily download, build, install, upgrade, and uninstall Python packages" groups = ["dev", "docs", "full", "linting", "opentelemetry"] files = [ - {file = "setuptools-69.2.0-py3-none-any.whl", hash = "sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c"}, - {file = "setuptools-69.2.0.tar.gz", hash = "sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e"}, + {file = "setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32"}, + {file = "setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987"}, ] [[package]] name = "shibuya" -version = "2024.4.8" +version = "2024.5.15" requires_python = ">=3.7" summary = "A clean, responsive, and customizable Sphinx documentation theme with light/dark mode." groups = ["docs"] @@ -3427,8 +3499,8 @@ dependencies = [ "Sphinx", ] files = [ - {file = "shibuya-2024.4.8-py3-none-any.whl", hash = "sha256:ba5f3bb63342845139f88bbf3d2d0282d6db49e7a9f6c044efb1baade4d9808b"}, - {file = "shibuya-2024.4.8.tar.gz", hash = "sha256:e9e4f21b9fe6cfa8a08dc66bf8f9d88a95ccc196bc9cdc7048cfbecb03b38f5d"}, + {file = "shibuya-2024.5.15-py3-none-any.whl", hash = "sha256:85a2338b6a900ade614d1f15533604672a9e55826ecc86617090b25fb4f05f50"}, + {file = "shibuya-2024.5.15.tar.gz", hash = "sha256:4053a79f97debf07de154812681aa86639c9eaaa845fa87f85611ac82b8f6019"}, ] [[package]] @@ -3548,7 +3620,7 @@ files = [ [[package]] name = "sphinx-autodoc-typehints" -version = "2.0.0" +version = "2.0.1" requires_python = ">=3.8" summary = "Type hints (PEP 484) support for the Sphinx autodoc extension" groups = ["docs"] @@ -3556,24 +3628,24 @@ dependencies = [ "sphinx>=7.1.2", ] files = [ - {file = "sphinx_autodoc_typehints-2.0.0-py3-none-any.whl", hash = "sha256:12c0e161f6fe191c2cdfd8fa3caea271f5387d9fbc67ebcd6f4f1f24ce880993"}, - {file = "sphinx_autodoc_typehints-2.0.0.tar.gz", hash = "sha256:7f2cdac2e70fd9787926b6e9e541cd4ded1e838d2b46fda2a1bb0a75ec5b7f3a"}, + {file = "sphinx_autodoc_typehints-2.0.1-py3-none-any.whl", hash = "sha256:f73ae89b43a799e587e39266672c1075b2ef783aeb382d3ebed77c38a3fc0149"}, + {file = "sphinx_autodoc_typehints-2.0.1.tar.gz", hash = "sha256:60ed1e3b2c970acc0aa6e877be42d48029a9faec7378a17838716cacd8c10b12"}, ] [[package]] name = "sphinx-click" -version = "5.1.0" +version = "6.0.0" requires_python = ">=3.8" summary = "Sphinx extension that automatically documents click applications" groups = ["docs"] dependencies = [ - "click>=7.0", + "click>=8.0", "docutils", - "sphinx>=2.0", + "sphinx>=4.0", ] files = [ - {file = "sphinx-click-5.1.0.tar.gz", hash = "sha256:6812c2db62d3fae71a4addbe5a8a0a16c97eb491f3cd63fe34b4ed7e07236f33"}, - {file = "sphinx_click-5.1.0-py3-none-any.whl", hash = "sha256:ae97557a4e9ec646045089326c3b90e026c58a45e083b8f35f17d5d6558d08a0"}, + {file = "sphinx_click-6.0.0-py3-none-any.whl", hash = "sha256:1e0a3c83bcb7c55497751b19d07ebe56b5d7b85eb76dd399cf9061b497adc317"}, + {file = "sphinx_click-6.0.0.tar.gz", hash = "sha256:f5d664321dc0c6622ff019f1e1c84e58ce0cecfddeb510e004cf60c2a3ab465b"}, ] [[package]] @@ -3786,7 +3858,7 @@ files = [ [[package]] name = "sqlalchemy" -version = "2.0.29" +version = "2.0.30" requires_python = ">=3.7" summary = "Database Abstraction Library" groups = ["docs", "full", "sqlalchemy"] @@ -3795,48 +3867,48 @@ dependencies = [ "typing-extensions>=4.6.0", ] files = [ - {file = "SQLAlchemy-2.0.29-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4c142852ae192e9fe5aad5c350ea6befe9db14370b34047e1f0f7cf99e63c63b"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:99a1e69d4e26f71e750e9ad6fdc8614fbddb67cfe2173a3628a2566034e223c7"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ef3fbccb4058355053c51b82fd3501a6e13dd808c8d8cd2561e610c5456013c"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d6753305936eddc8ed190e006b7bb33a8f50b9854823485eed3a886857ab8d1"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0f3ca96af060a5250a8ad5a63699180bc780c2edf8abf96c58af175921df847a"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c4520047006b1d3f0d89e0532978c0688219857eb2fee7c48052560ae76aca1e"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-win32.whl", hash = "sha256:b2a0e3cf0caac2085ff172c3faacd1e00c376e6884b5bc4dd5b6b84623e29e4f"}, - {file = "SQLAlchemy-2.0.29-cp310-cp310-win_amd64.whl", hash = "sha256:01d10638a37460616708062a40c7b55f73e4d35eaa146781c683e0fa7f6c43fb"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:308ef9cb41d099099fffc9d35781638986870b29f744382904bf9c7dadd08513"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:296195df68326a48385e7a96e877bc19aa210e485fa381c5246bc0234c36c78e"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a13b917b4ffe5a0a31b83d051d60477819ddf18276852ea68037a144a506efb9"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f6d971255d9ddbd3189e2e79d743ff4845c07f0633adfd1de3f63d930dbe673"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:61405ea2d563407d316c63a7b5271ae5d274a2a9fbcd01b0aa5503635699fa1e"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:de7202ffe4d4a8c1e3cde1c03e01c1a3772c92858837e8f3879b497158e4cb44"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-win32.whl", hash = "sha256:b5d7ed79df55a731749ce65ec20d666d82b185fa4898430b17cb90c892741520"}, - {file = "SQLAlchemy-2.0.29-cp311-cp311-win_amd64.whl", hash = "sha256:205f5a2b39d7c380cbc3b5dcc8f2762fb5bcb716838e2d26ccbc54330775b003"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d96710d834a6fb31e21381c6d7b76ec729bd08c75a25a5184b1089141356171f"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:52de4736404e53c5c6a91ef2698c01e52333988ebdc218f14c833237a0804f1b"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c7b02525ede2a164c5fa5014915ba3591730f2cc831f5be9ff3b7fd3e30958e"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dfefdb3e54cd15f5d56fd5ae32f1da2d95d78319c1f6dfb9bcd0eb15d603d5d"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a88913000da9205b13f6f195f0813b6ffd8a0c0c2bd58d499e00a30eb508870c"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fecd5089c4be1bcc37c35e9aa678938d2888845a134dd016de457b942cf5a758"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-win32.whl", hash = "sha256:8197d6f7a3d2b468861ebb4c9f998b9df9e358d6e1cf9c2a01061cb9b6cf4e41"}, - {file = "SQLAlchemy-2.0.29-cp312-cp312-win_amd64.whl", hash = "sha256:9b19836ccca0d321e237560e475fd99c3d8655d03da80c845c4da20dda31b6e1"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7e614d7a25a43a9f54fcce4675c12761b248547f3d41b195e8010ca7297c369c"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:471fcb39c6adf37f820350c28aac4a7df9d3940c6548b624a642852e727ea586"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:988569c8732f54ad3234cf9c561364221a9e943b78dc7a4aaf35ccc2265f1930"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dddaae9b81c88083e6437de95c41e86823d150f4ee94bf24e158a4526cbead01"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:334184d1ab8f4c87f9652b048af3f7abea1c809dfe526fb0435348a6fef3d380"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:38b624e5cf02a69b113c8047cf7f66b5dfe4a2ca07ff8b8716da4f1b3ae81567"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-win32.whl", hash = "sha256:bab41acf151cd68bc2b466deae5deeb9e8ae9c50ad113444151ad965d5bf685b"}, - {file = "SQLAlchemy-2.0.29-cp38-cp38-win_amd64.whl", hash = "sha256:52c8011088305476691b8750c60e03b87910a123cfd9ad48576d6414b6ec2a1d"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3071ad498896907a5ef756206b9dc750f8e57352113c19272bdfdc429c7bd7de"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dba622396a3170974f81bad49aacebd243455ec3cc70615aeaef9e9613b5bca5"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b184e3de58009cc0bf32e20f137f1ec75a32470f5fede06c58f6c355ed42a72"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c37f1050feb91f3d6c32f864d8e114ff5545a4a7afe56778d76a9aec62638ba"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bda7ce59b06d0f09afe22c56714c65c957b1068dee3d5e74d743edec7daba552"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:25664e18bef6dc45015b08f99c63952a53a0a61f61f2e48a9e70cec27e55f699"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-win32.whl", hash = "sha256:77d29cb6c34b14af8a484e831ab530c0f7188f8efed1c6a833a2c674bf3c26ec"}, - {file = "SQLAlchemy-2.0.29-cp39-cp39-win_amd64.whl", hash = "sha256:04c487305ab035a9548f573763915189fc0fe0824d9ba28433196f8436f1449c"}, - {file = "SQLAlchemy-2.0.29-py3-none-any.whl", hash = "sha256:dc4ee2d4ee43251905f88637d5281a8d52e916a021384ec10758826f5cbae305"}, - {file = "SQLAlchemy-2.0.29.tar.gz", hash = "sha256:bd9566b8e58cabd700bc367b60e90d9349cd16f0984973f98a9a09f9c64e86f0"}, + {file = "SQLAlchemy-2.0.30-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3b48154678e76445c7ded1896715ce05319f74b1e73cf82d4f8b59b46e9c0ddc"}, + {file = "SQLAlchemy-2.0.30-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2753743c2afd061bb95a61a51bbb6a1a11ac1c44292fad898f10c9839a7f75b2"}, + {file = "SQLAlchemy-2.0.30-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7bfc726d167f425d4c16269a9a10fe8630ff6d14b683d588044dcef2d0f6be7"}, + {file = "SQLAlchemy-2.0.30-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4f61ada6979223013d9ab83a3ed003ded6959eae37d0d685db2c147e9143797"}, + {file = "SQLAlchemy-2.0.30-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a365eda439b7a00732638f11072907c1bc8e351c7665e7e5da91b169af794af"}, + {file = "SQLAlchemy-2.0.30-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bba002a9447b291548e8d66fd8c96a6a7ed4f2def0bb155f4f0a1309fd2735d5"}, + {file = "SQLAlchemy-2.0.30-cp310-cp310-win32.whl", hash = "sha256:0138c5c16be3600923fa2169532205d18891b28afa817cb49b50e08f62198bb8"}, + {file = "SQLAlchemy-2.0.30-cp310-cp310-win_amd64.whl", hash = "sha256:99650e9f4cf3ad0d409fed3eec4f071fadd032e9a5edc7270cd646a26446feeb"}, + {file = "SQLAlchemy-2.0.30-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:955991a09f0992c68a499791a753523f50f71a6885531568404fa0f231832aa0"}, + {file = "SQLAlchemy-2.0.30-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f69e4c756ee2686767eb80f94c0125c8b0a0b87ede03eacc5c8ae3b54b99dc46"}, + {file = "SQLAlchemy-2.0.30-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69c9db1ce00e59e8dd09d7bae852a9add716efdc070a3e2068377e6ff0d6fdaa"}, + {file = "SQLAlchemy-2.0.30-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1429a4b0f709f19ff3b0cf13675b2b9bfa8a7e79990003207a011c0db880a13"}, + {file = "SQLAlchemy-2.0.30-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:efedba7e13aa9a6c8407c48facfdfa108a5a4128e35f4c68f20c3407e4376aa9"}, + {file = "SQLAlchemy-2.0.30-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:16863e2b132b761891d6c49f0a0f70030e0bcac4fd208117f6b7e053e68668d0"}, + {file = "SQLAlchemy-2.0.30-cp311-cp311-win32.whl", hash = "sha256:2ecabd9ccaa6e914e3dbb2aa46b76dede7eadc8cbf1b8083c94d936bcd5ffb49"}, + {file = "SQLAlchemy-2.0.30-cp311-cp311-win_amd64.whl", hash = "sha256:0b3f4c438e37d22b83e640f825ef0f37b95db9aa2d68203f2c9549375d0b2260"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5a79d65395ac5e6b0c2890935bad892eabb911c4aa8e8015067ddb37eea3d56c"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9a5baf9267b752390252889f0c802ea13b52dfee5e369527da229189b8bd592e"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cb5a646930c5123f8461f6468901573f334c2c63c795b9af350063a736d0134"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:296230899df0b77dec4eb799bcea6fbe39a43707ce7bb166519c97b583cfcab3"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c62d401223f468eb4da32627bffc0c78ed516b03bb8a34a58be54d618b74d472"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3b69e934f0f2b677ec111b4d83f92dc1a3210a779f69bf905273192cf4ed433e"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-win32.whl", hash = "sha256:77d2edb1f54aff37e3318f611637171e8ec71472f1fdc7348b41dcb226f93d90"}, + {file = "SQLAlchemy-2.0.30-cp312-cp312-win_amd64.whl", hash = "sha256:b6c7ec2b1f4969fc19b65b7059ed00497e25f54069407a8701091beb69e591a5"}, + {file = "SQLAlchemy-2.0.30-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc0c53579650a891f9b83fa3cecd4e00218e071d0ba00c4890f5be0c34887ed3"}, + {file = "SQLAlchemy-2.0.30-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:311710f9a2ee235f1403537b10c7687214bb1f2b9ebb52702c5aa4a77f0b3af7"}, + {file = "SQLAlchemy-2.0.30-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:408f8b0e2c04677e9c93f40eef3ab22f550fecb3011b187f66a096395ff3d9fd"}, + {file = "SQLAlchemy-2.0.30-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37a4b4fb0dd4d2669070fb05b8b8824afd0af57587393015baee1cf9890242d9"}, + {file = "SQLAlchemy-2.0.30-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a943d297126c9230719c27fcbbeab57ecd5d15b0bd6bfd26e91bfcfe64220621"}, + {file = "SQLAlchemy-2.0.30-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0a089e218654e740a41388893e090d2e2c22c29028c9d1353feb38638820bbeb"}, + {file = "SQLAlchemy-2.0.30-cp38-cp38-win32.whl", hash = "sha256:fa561138a64f949f3e889eb9ab8c58e1504ab351d6cf55259dc4c248eaa19da6"}, + {file = "SQLAlchemy-2.0.30-cp38-cp38-win_amd64.whl", hash = "sha256:7d74336c65705b986d12a7e337ba27ab2b9d819993851b140efdf029248e818e"}, + {file = "SQLAlchemy-2.0.30-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ae8c62fe2480dd61c532ccafdbce9b29dacc126fe8be0d9a927ca3e699b9491a"}, + {file = "SQLAlchemy-2.0.30-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2383146973a15435e4717f94c7509982770e3e54974c71f76500a0136f22810b"}, + {file = "SQLAlchemy-2.0.30-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8409de825f2c3b62ab15788635ccaec0c881c3f12a8af2b12ae4910a0a9aeef6"}, + {file = "SQLAlchemy-2.0.30-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0094c5dc698a5f78d3d1539853e8ecec02516b62b8223c970c86d44e7a80f6c7"}, + {file = "SQLAlchemy-2.0.30-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:edc16a50f5e1b7a06a2dcc1f2205b0b961074c123ed17ebda726f376a5ab0953"}, + {file = "SQLAlchemy-2.0.30-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f7703c2010355dd28f53deb644a05fc30f796bd8598b43f0ba678878780b6e4c"}, + {file = "SQLAlchemy-2.0.30-cp39-cp39-win32.whl", hash = "sha256:1f9a727312ff6ad5248a4367358e2cf7e625e98b1028b1d7ab7b806b7d757513"}, + {file = "SQLAlchemy-2.0.30-cp39-cp39-win_amd64.whl", hash = "sha256:a0ef36b28534f2a5771191be6edb44cc2673c7b2edf6deac6562400288664221"}, + {file = "SQLAlchemy-2.0.30-py3-none-any.whl", hash = "sha256:7108d569d3990c71e26a42f60474b4c02c8586c4681af5fd67e51a044fdea86a"}, + {file = "SQLAlchemy-2.0.30.tar.gz", hash = "sha256:2b1708916730f4830bc69d6f49d37f7698b5bd7530aca7f04f785f8849e95255"}, ] [[package]] @@ -3905,6 +3977,108 @@ files = [ {file = "taskgroup-0.0.0a4.tar.gz", hash = "sha256:eb08902d221e27661950f2a0320ddf3f939f579279996f81fe30779bca3a159c"}, ] +[[package]] +name = "test-results-parser" +version = "0.1.0" +requires_python = ">=3.8" +summary = "" +groups = ["linting"] +files = [ + {file = "test_results_parser-0.1.0-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:78457dd51966244ab144b8d726379a404075ce14cb8d0591d498293d22a7b628"}, + {file = "test_results_parser-0.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3dd544e668525afdcbdf77ce5f321501ff061af0f7763d5da6766909c08c5a32"}, + {file = "test_results_parser-0.1.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:66a2424b9d915de8be516789c93b73eb26168f868153811865949d32f0da64da"}, + {file = "test_results_parser-0.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c33eb93a0e5562a1172f369500174e18c9f45e8ccbdd784315d6c3cff8ce4c5"}, + {file = "test_results_parser-0.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:02286c4735abde145f2a5e17e73778bdd29af2608cf01cd7b422ed23d3fbbc94"}, + {file = "test_results_parser-0.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:adb185a9657f9fc1ed6501313eec897f5dc2fa6460d07f5a2e54f7aa8c365e9a"}, + {file = "test_results_parser-0.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c09b4672e15fec1ffb3059d36ba7d7a20830297a73c65af0fbaac317ec02f359"}, + {file = "test_results_parser-0.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ec0aabe0e91933a35603e5e90a2a8c49b92af3b3edc5fe37315f9cf7dc5e4aec"}, + {file = "test_results_parser-0.1.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:46ce4d77883afc9e5f7f6158fc54ddb8088dcb65df731d8a820b64ff3e2e3c62"}, + {file = "test_results_parser-0.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:bed2dd0e6b63200949046bfd13dea28e23d01fbc3796a5740c17e3f21f3c9152"}, + {file = "test_results_parser-0.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:314bd28eb9c4f5c9482ec9ce7679b4f1eb74f179227c27d4566bd8d6c3f0af70"}, + {file = "test_results_parser-0.1.0-cp310-none-win32.whl", hash = "sha256:e42d10b29609ed56199008e0047ba881f5e15ab39509d854dc5c22144ff26058"}, + {file = "test_results_parser-0.1.0-cp310-none-win_amd64.whl", hash = "sha256:f70ba9bb0550b8d1d2e3c48b74df6bb07f4a265a5deacb6cd6829955a82e65ef"}, + {file = "test_results_parser-0.1.0-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:dc2c4f5accaa9eb5be6cb251a494f2af33ee7b31fadf94a5b77d4649efe14848"}, + {file = "test_results_parser-0.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25c147e9b14a464cf67887601b5dc7882ddf595be74ad8ad57f62131710ba561"}, + {file = "test_results_parser-0.1.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0083fa371cb12ca1b5b4dd1d0a38c29bed45f7581767e13882426ff7e7582af1"}, + {file = "test_results_parser-0.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fd9eda302a7583e6da8b2ce492073cc70dcadcd5264d4410891ca2918afe854d"}, + {file = "test_results_parser-0.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2cf06980539a1c1aa7d904ba0ea6c997215689386b6b274e90e699dde9cf55b"}, + {file = "test_results_parser-0.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6e89353a3de9f42b87939d525392b6f485c687f0a3c214e7fdf9f0bca1045eb"}, + {file = "test_results_parser-0.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8362eebf1df1ad22502c75c8b9a8d13520bd292fa6efb26466e1402e621238d"}, + {file = "test_results_parser-0.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:000e1cc7f5dd98ef0d6927a873bd169223a8f4f10b85f53c55e5a43c67342e9f"}, + {file = "test_results_parser-0.1.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:b592a301e62914a7c06bbaf9694b25f98d45f0b0dac2d8d86144fd7056225bb2"}, + {file = "test_results_parser-0.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:559dab14cc88fc10fb8a9627be3b50ce05fea7f50d95716d8d8746fdcb601c41"}, + {file = "test_results_parser-0.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8bf5944652c65e827e8698651771763fe9cea3602c7cc39358b25601cac83b52"}, + {file = "test_results_parser-0.1.0-cp311-none-win32.whl", hash = "sha256:cd629aa1c1ae3cd68b1ab77b4fa7a7e5878e18fd69b8f87306540277c389c8a6"}, + {file = "test_results_parser-0.1.0-cp311-none-win_amd64.whl", hash = "sha256:620ac0a71fa07225bec8fab9e311950a904e26ea45846d32ef4f9d9addaf236c"}, + {file = "test_results_parser-0.1.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:ff72b9aa40be8ab6d8d68e6d02ac7dddec5f0e4762c5271d69fbc81de4854d09"}, + {file = "test_results_parser-0.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf7baedb05e1006effe0bfeab04934965058969dc647a63cb341370e92e6f0ca"}, + {file = "test_results_parser-0.1.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1046f5e90976ccfe3aa51bcb912fbf3e23991b41a8a836ee22381f3e292ddf79"}, + {file = "test_results_parser-0.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:61070f24114fa96d6102ad02e6a4ed7acd9e962963687657ab3b835925cd381f"}, + {file = "test_results_parser-0.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd488964632e1a55b4472547f23c3681b925e7828b955e0dbf46a69edd6d4fb7"}, + {file = "test_results_parser-0.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76c01f61d28c620fda604cfd8a87e4885884fa77ad0db80c84bfc93212f10027"}, + {file = "test_results_parser-0.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4e289d55afc1f7440dad3c56485dcab86800d922669a6ab2b10f113a10196a73"}, + {file = "test_results_parser-0.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e628b80b734f8b3aa1b2464a935b34cd06de22286ea14ad4cc0a6b3b09fc1601"}, + {file = "test_results_parser-0.1.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:69614e69b830dcb2f9a0e7c3cbc1b7efdd399e317087cd2be7f8ce0ab45ed182"}, + {file = "test_results_parser-0.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:557b5b2766631f0f9caedb6a70d3dcd1628bcd6a8c5d5545c641b9e5c645cb17"}, + {file = "test_results_parser-0.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:db57956b1f7d546196c2ea2ead2469bca70f5e51053e3e5ab0f11fa051b134a2"}, + {file = "test_results_parser-0.1.0-cp312-none-win32.whl", hash = "sha256:592564e7ccf2febe72ead637a90b8bb8c1d7b5ad77000cf2991442c22b4a45ac"}, + {file = "test_results_parser-0.1.0-cp312-none-win_amd64.whl", hash = "sha256:c7c063f565d0eda32e7dacef75b38d120412d934543f6826e0a6d3f3778c4ef9"}, + {file = "test_results_parser-0.1.0-cp38-cp38-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e7acb420f740e09c25970dddb3d51cc30270ec92c0cdf22b79f6fa94934246d8"}, + {file = "test_results_parser-0.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62b0b1b9c06d89ef139543878c0de3c8401ede38793ae5d2beabf150fa108f20"}, + {file = "test_results_parser-0.1.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ad619bdb6d1eef0eaa56a8b14e22cc5781d08432c36d05c24eafe14889c1ae7"}, + {file = "test_results_parser-0.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1acdb3a52795c556235cf7ee977afb2e9969a8cd12eb24a070ac859363df7004"}, + {file = "test_results_parser-0.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d3b931f99726b37c7e29e4dfc233963a152c39ffaf1a433598c2014a45b29fca"}, + {file = "test_results_parser-0.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1603383ad6c3245dc2f7407a24742c99a0f5fed86a1322752ce6b35daf800caf"}, + {file = "test_results_parser-0.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:74dbe45d3e848d9253eebeca08af38523da2a7e9a16829bbee59287a50e1ffef"}, + {file = "test_results_parser-0.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:3bd0c8bc467538d01e23168c34429ae92fb5adc5ddc23655cd10bad94c3b01ab"}, + {file = "test_results_parser-0.1.0-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:587111a3af0f38f8ffc3c6dc8d7f5d2c5b67dd5d83e1d57c6d5c2ef5d1b6b749"}, + {file = "test_results_parser-0.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:a48a11ce684ee09d15aefd1e22ff0969f86af6c94ddf11281d504d7319601d6e"}, + {file = "test_results_parser-0.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:15ac06f83f81bd2f39a929e043fcb44caa8da601ec7b03fa864e00b69a320f7d"}, + {file = "test_results_parser-0.1.0-cp38-none-win32.whl", hash = "sha256:9ec2d62c148dbbf43eb9dcad861f54101cc4f3e070acf131fe4bb59989b7bad8"}, + {file = "test_results_parser-0.1.0-cp38-none-win_amd64.whl", hash = "sha256:7223dda9ced5c2be5939667f7fbf7de08e0020bd2d6a6e8c1799a10c02d00fb2"}, + {file = "test_results_parser-0.1.0-cp39-cp39-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:632232864025913a0e71365732cfe507cf3b1262000cc1753cb97139ed8c493e"}, + {file = "test_results_parser-0.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a64a894c8bb2542925ba8fc9199939161eea4d5d56ea59b21e9627185f658a63"}, + {file = "test_results_parser-0.1.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:db6e4dbafdf7bec092cd34cd971634fc1842e26d2936636f684892c4755afab9"}, + {file = "test_results_parser-0.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:179ff5c2d470a1edcd1965a9cec03b00b615d6dc31d57bb85b233b52d4a54076"}, + {file = "test_results_parser-0.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9a72194747797654397ad615c38ac0e9c9ef56bbc078a2005110d5010a06ee6c"}, + {file = "test_results_parser-0.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:384ecace49bfc68a2b33dd8266598430ccd56a579e7157ede9b1c5ffbe9c286f"}, + {file = "test_results_parser-0.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5abf2505bb09500e09cbe54cc8bfbcfbc501a8f986afdbcffd13ebab449693e5"}, + {file = "test_results_parser-0.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1bd6f46369ae86412f9bfdd453788d32c84d1ceb9ef0056081229ce384fbbf89"}, + {file = "test_results_parser-0.1.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:a32b3afb5509d06652ab08d242c655854431d25c7059b865d5c8bb376ba83278"}, + {file = "test_results_parser-0.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:9ff24b1f6f2ee0d29f261d9f1ee5752030ac90ab5875d954d5208ea3ab8a1b41"}, + {file = "test_results_parser-0.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2e48ac37891c5a4b43e2d5ab45727c03b0323fc5a3ee6b671fc9aed012394a57"}, + {file = "test_results_parser-0.1.0-cp39-none-win32.whl", hash = "sha256:f181a3c80807207463c946dd500e21ad506143f5f4daa48054cd3ec04c0e3de2"}, + {file = "test_results_parser-0.1.0-cp39-none-win_amd64.whl", hash = "sha256:d90300ead999d0e7313debbc1773853ffc37aa17139f017bdf3b1645f80c808b"}, + {file = "test_results_parser-0.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b4732d69e9550bb31043e73d1d3acdc8eb30b76414607fc220d28e1867ef70e"}, + {file = "test_results_parser-0.1.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6e039e16391d0a8056b9536d0318b8044e1335b1164917272ccbea82e4ded3c0"}, + {file = "test_results_parser-0.1.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:673d4f04dbdb932caa3d8c7b9a026e56c2d878b3474ed2d814ca42bc8f4066a9"}, + {file = "test_results_parser-0.1.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a39c5f6ccf5c0d742baf99149fd0edc3cef1abbfc6c66d38466fc08a67f090f"}, + {file = "test_results_parser-0.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d28b3dfee937e43c9256fa484110f8a1ea8bfcb8baeef67aabe3f2c930ae335"}, + {file = "test_results_parser-0.1.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b11832276f16612f2e99c80d901c8998598604dc5b281cdbf5adf0b4253505b9"}, + {file = "test_results_parser-0.1.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:a53a6798f6de9afc674064498f890c6ac2dfb70212293b8ea4f7acec5042e3d9"}, + {file = "test_results_parser-0.1.0-pp310-pypy310_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:e22b8ad9e62138f7384a86bfad78edc16474f18cfd2e2cd9e65b55474a245345"}, + {file = "test_results_parser-0.1.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:f421ee9abcc664e0d9713cff60ba020b7390abe03262b22c1c5acbae7b235223"}, + {file = "test_results_parser-0.1.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5134f3175586de645a2775cef6519343daaae243d0301d44ee7867075f581a7"}, + {file = "test_results_parser-0.1.0-pp38-pypy38_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9e41d2c6ef9a154e1dd4dbd83de906aaab7a9fdd33be7e871110401cde35af57"}, + {file = "test_results_parser-0.1.0-pp38-pypy38_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:924100e5cb43c8b42bc63946061a8899c750244cb97881f1828ccde06265b214"}, + {file = "test_results_parser-0.1.0-pp38-pypy38_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13b9a4d61e1404f5d3889716fdee90018d330a722680b6cf2fc381f298134c3a"}, + {file = "test_results_parser-0.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd7952ba5dfc97970ead49722b7ea61318b29602d0814f215efa52317e896486"}, + {file = "test_results_parser-0.1.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:61efc73848f901f5af295420f8346fa09c4337ceb8b769dff9134e388b1275e9"}, + {file = "test_results_parser-0.1.0-pp38-pypy38_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:70451bbaa38c94ffe3ba6fc7c11d30b56b7e84d8cc3bb891580f43d87f3a5f49"}, + {file = "test_results_parser-0.1.0-pp38-pypy38_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:7bfae8192331c1d11364967d9c7978efb9fad0a033bff42f4f758d846f50a274"}, + {file = "test_results_parser-0.1.0-pp38-pypy38_pp73-musllinux_1_2_i686.whl", hash = "sha256:3821020b4ffe3e8db51b12fba39381b9537b426acc590a5fdfb77ca96396448f"}, + {file = "test_results_parser-0.1.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2efb560cb6d5a1623dcd8e5c58adc62b1bc236ff226983116c8dedb1357255fb"}, + {file = "test_results_parser-0.1.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b3080b3b90014c1296ec7d5ef78300fce204f33a71865598921fed888f62e0f"}, + {file = "test_results_parser-0.1.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:998ed8d9979dc40c8ace7b1e6e2a5c901b1e36519a7ad35d1ca728135d48d789"}, + {file = "test_results_parser-0.1.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6bfb712e6e1bff252ed2a5de5bce527a17a0a96666fd827e8515acaa5d2c064b"}, + {file = "test_results_parser-0.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8d58e4e2f56bd032dd9a6e509da79f7509887cc386f2a736eeec2c2024cdc32"}, + {file = "test_results_parser-0.1.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43a46bc8de43e44da870a4ed939ed8545c80210f090bb545498868dbbb3ba291"}, + {file = "test_results_parser-0.1.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:97f8d6e0a5959b0c5a3180288c576136e4334167f4adef8eebebca3462d8f292"}, + {file = "test_results_parser-0.1.0-pp39-pypy39_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:960e2eeaccf6794e5963afc05aa920ff17d66bbe96b870150e2fa214d7db5c21"}, + {file = "test_results_parser-0.1.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:682ef893c500634a89a0b7b0da247354921de02ccbe9c541796d4fa016722317"}, + {file = "test_results_parser-0.1.0.tar.gz", hash = "sha256:0034281a4b406d7f072fc5ac1f5e44660e3c23bc92f2e7284862ee097f9626ee"}, +] + [[package]] name = "time-machine" version = "2.14.1" @@ -4093,7 +4267,7 @@ files = [ [[package]] name = "trio" -version = "0.25.0" +version = "0.25.1" requires_python = ">=3.8" summary = "A friendly Python library for async concurrency and I/O" groups = ["dev"] @@ -4107,8 +4281,8 @@ dependencies = [ "sortedcontainers", ] files = [ - {file = "trio-0.25.0-py3-none-any.whl", hash = "sha256:e6458efe29cc543e557a91e614e2b51710eba2961669329ce9c862d50c6e8e81"}, - {file = "trio-0.25.0.tar.gz", hash = "sha256:9b41f5993ad2c0e5f62d0acca320ec657fdb6b2a2c22b8c7aed6caf154475c4e"}, + {file = "trio-0.25.1-py3-none-any.whl", hash = "sha256:e42617ba091e7b2e50c899052e83a3c403101841de925187f61e7b7eaebdf3fb"}, + {file = "trio-0.25.1.tar.gz", hash = "sha256:9f5314f014ea3af489e77b001861c535005c3858d38ec46b6b071ebfa339d7fb"}, ] [[package]] @@ -4187,7 +4361,7 @@ files = [ [[package]] name = "types-beautifulsoup4" -version = "4.12.0.20240229" +version = "4.12.0.20240511" requires_python = ">=3.8" summary = "Typing stubs for beautifulsoup4" groups = ["linting"] @@ -4195,8 +4369,22 @@ dependencies = [ "types-html5lib", ] files = [ - {file = "types-beautifulsoup4-4.12.0.20240229.tar.gz", hash = "sha256:e37e4cfa11b03b01775732e56d2c010cb24ee107786277bae6bc0fa3e305b686"}, - {file = "types_beautifulsoup4-4.12.0.20240229-py3-none-any.whl", hash = "sha256:000cdddb8aee4effb45a04be95654de8629fb8594a4f2f1231cff81108977324"}, + {file = "types-beautifulsoup4-4.12.0.20240511.tar.gz", hash = "sha256:004f6096fdd83b19cdbf6cb10e4eae57b10205eccc365d0a69d77da836012e28"}, + {file = "types_beautifulsoup4-4.12.0.20240511-py3-none-any.whl", hash = "sha256:7ceda66a93ba28d759d5046d7fec9f4cad2f563a77b3a789efc90bcadafeefd1"}, +] + +[[package]] +name = "types-cffi" +version = "1.16.0.20240331" +requires_python = ">=3.8" +summary = "Typing stubs for cffi" +groups = ["linting"] +dependencies = [ + "types-setuptools", +] +files = [ + {file = "types-cffi-1.16.0.20240331.tar.gz", hash = "sha256:b8b20d23a2b89cfed5f8c5bc53b0cb8677c3aac6d970dbc771e28b9c698f5dee"}, + {file = "types_cffi-1.16.0.20240331-py3-none-any.whl", hash = "sha256:a363e5ea54a4eb6a4a105d800685fde596bc318089b025b27dee09849fe41ff0"}, ] [[package]] @@ -4212,13 +4400,13 @@ files = [ [[package]] name = "types-psutil" -version = "5.9.5.20240316" +version = "5.9.5.20240516" requires_python = ">=3.8" summary = "Typing stubs for psutil" groups = ["linting"] files = [ - {file = "types-psutil-5.9.5.20240316.tar.gz", hash = "sha256:5636f5714bb930c64bb34c4d47a59dc92f9d610b778b5364a31daa5584944848"}, - {file = "types_psutil-5.9.5.20240316-py3-none-any.whl", hash = "sha256:2fdd64ea6e97befa546938f486732624f9255fde198b55e6f00fda236f059f64"}, + {file = "types-psutil-5.9.5.20240516.tar.gz", hash = "sha256:bb296f59fc56458891d0feb1994717e548a1bcf89936a2877df8792b822b4696"}, + {file = "types_psutil-5.9.5.20240516-py3-none-any.whl", hash = "sha256:83146ded949a10167d9895e567b3b71e53ebc5e23fd8363eab62b3c76cce7b89"}, ] [[package]] @@ -4234,16 +4422,17 @@ files = [ [[package]] name = "types-pyopenssl" -version = "24.0.0.20240311" +version = "24.1.0.20240425" requires_python = ">=3.8" summary = "Typing stubs for pyOpenSSL" groups = ["linting"] dependencies = [ "cryptography>=35.0.0", + "types-cffi", ] files = [ - {file = "types-pyOpenSSL-24.0.0.20240311.tar.gz", hash = "sha256:7bca00cfc4e7ef9c5d2663c6a1c068c35798e59670595439f6296e7ba3d58083"}, - {file = "types_pyOpenSSL-24.0.0.20240311-py3-none-any.whl", hash = "sha256:6e8e8bfad34924067333232c93f7fc4b369856d8bea0d5c9d1808cb290ab1972"}, + {file = "types-pyOpenSSL-24.1.0.20240425.tar.gz", hash = "sha256:0a7e82626c1983dc8dc59292bf20654a51c3c3881bcbb9b337c1da6e32f0204e"}, + {file = "types_pyOpenSSL-24.1.0.20240425-py3-none-any.whl", hash = "sha256:f51a156835555dd2a1f025621e8c4fbe7493470331afeef96884d1d29bf3a473"}, ] [[package]] @@ -4273,7 +4462,7 @@ files = [ [[package]] name = "types-redis" -version = "4.6.0.20240409" +version = "4.6.0.20240425" requires_python = ">=3.8" summary = "Typing stubs for redis" groups = ["linting"] @@ -4282,19 +4471,30 @@ dependencies = [ "types-pyOpenSSL", ] files = [ - {file = "types-redis-4.6.0.20240409.tar.gz", hash = "sha256:ce217c279581d769df992c5b76d61c65425b0a679626048e633e643868eb881b"}, - {file = "types_redis-4.6.0.20240409-py3-none-any.whl", hash = "sha256:a3b92760c49a034827a0c3825206728df4e61e981c1324099d4414335af4f52f"}, + {file = "types-redis-4.6.0.20240425.tar.gz", hash = "sha256:9402a10ee931d241fdfcc04592ebf7a661d7bb92a8dea631279f0d8acbcf3a22"}, + {file = "types_redis-4.6.0.20240425-py3-none-any.whl", hash = "sha256:ac5bc19e8f5997b9e76ad5d9cf15d0392d9f28cf5fc7746ea4a64b989c45c6a8"}, +] + +[[package]] +name = "types-setuptools" +version = "69.5.0.20240423" +requires_python = ">=3.8" +summary = "Typing stubs for setuptools" +groups = ["linting"] +files = [ + {file = "types-setuptools-69.5.0.20240423.tar.gz", hash = "sha256:a7ba908f1746c4337d13f027fa0f4a5bcad6d1d92048219ba792b3295c58586d"}, + {file = "types_setuptools-69.5.0.20240423-py3-none-any.whl", hash = "sha256:a4381e041510755a6c9210e26ad55b1629bc10237aeb9cb8b6bd24996b73db48"}, ] [[package]] name = "typing-extensions" -version = "4.11.0" +version = "4.12.0" requires_python = ">=3.8" summary = "Backported and Experimental Type Hints for Python 3.8+" groups = ["annotated-types", "cli", "default", "dev", "dev-contrib", "docs", "full", "linting", "opentelemetry", "piccolo", "pydantic", "sqlalchemy", "standard"] files = [ - {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, - {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, + {file = "typing_extensions-4.12.0-py3-none-any.whl", hash = "sha256:b349c66bea9016ac22978d800cfff206d5f9816951f12a7d0ec5578b0a819594"}, + {file = "typing_extensions-4.12.0.tar.gz", hash = "sha256:8cbcdc8606ebcb0d95453ad7dc5065e6237b6aa230a31e81d0f440c30fed5fd8"}, ] [[package]] @@ -4401,7 +4601,7 @@ files = [ [[package]] name = "virtualenv" -version = "20.25.1" +version = "20.26.1" requires_python = ">=3.7" summary = "Virtual Python Environment builder" groups = ["linting"] @@ -4411,8 +4611,8 @@ dependencies = [ "platformdirs<5,>=3.9.1", ] files = [ - {file = "virtualenv-20.25.1-py3-none-any.whl", hash = "sha256:961c026ac520bac5f69acb8ea063e8a4f071bcc9457b9c1f28f6b085c511583a"}, - {file = "virtualenv-20.25.1.tar.gz", hash = "sha256:e08e13ecdca7a0bd53798f356d5831434afa5b07b93f0abdf0797b7a06ffe197"}, + {file = "virtualenv-20.26.1-py3-none-any.whl", hash = "sha256:7aa9982a728ae5892558bff6a2839c00b9ed145523ece2274fad6f414690ae75"}, + {file = "virtualenv-20.26.1.tar.gz", hash = "sha256:604bfdceaeece392802e6ae48e69cec49168b9c5f4a44e483963f9242eb0e78b"}, ] [[package]] @@ -4692,7 +4892,7 @@ files = [ [[package]] name = "zope-interface" -version = "6.2" +version = "6.3" requires_python = ">=3.7" summary = "Interfaces for Python" groups = ["dev"] @@ -4700,35 +4900,35 @@ dependencies = [ "setuptools", ] files = [ - {file = "zope.interface-6.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:506f5410b36e5ba494136d9fa04c548eaf1a0d9c442b0b0e7a0944db7620e0ab"}, - {file = "zope.interface-6.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b386b8b9d2b6a5e1e4eadd4e62335571244cb9193b7328c2b6e38b64cfda4f0e"}, - {file = "zope.interface-6.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abb0b3f2cb606981c7432f690db23506b1db5899620ad274e29dbbbdd740e797"}, - {file = "zope.interface-6.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de7916380abaef4bb4891740879b1afcba2045aee51799dfd6d6ca9bdc71f35f"}, - {file = "zope.interface-6.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b240883fb43160574f8f738e6d09ddbdbf8fa3e8cea051603d9edfd947d9328"}, - {file = "zope.interface-6.2-cp310-cp310-win_amd64.whl", hash = "sha256:8af82afc5998e1f307d5e72712526dba07403c73a9e287d906a8aa2b1f2e33dd"}, - {file = "zope.interface-6.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4d45d2ba8195850e3e829f1f0016066a122bfa362cc9dc212527fc3d51369037"}, - {file = "zope.interface-6.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:76e0531d86523be7a46e15d379b0e975a9db84316617c0efe4af8338dc45b80c"}, - {file = "zope.interface-6.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59f7374769b326a217d0b2366f1c176a45a4ff21e8f7cebb3b4a3537077eff85"}, - {file = "zope.interface-6.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25e0af9663eeac6b61b231b43c52293c2cb7f0c232d914bdcbfd3e3bd5c182ad"}, - {file = "zope.interface-6.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14e02a6fc1772b458ebb6be1c276528b362041217b9ca37e52ecea2cbdce9fac"}, - {file = "zope.interface-6.2-cp311-cp311-win_amd64.whl", hash = "sha256:02adbab560683c4eca3789cc0ac487dcc5f5a81cc48695ec247f00803cafe2fe"}, - {file = "zope.interface-6.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8f5d2c39f3283e461de3655e03faf10e4742bb87387113f787a7724f32db1e48"}, - {file = "zope.interface-6.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:75d2ec3d9b401df759b87bc9e19d1b24db73083147089b43ae748aefa63067ef"}, - {file = "zope.interface-6.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa994e8937e8ccc7e87395b7b35092818905cf27c651e3ff3e7f29729f5ce3ce"}, - {file = "zope.interface-6.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ede888382882f07b9e4cd942255921ffd9f2901684198b88e247c7eabd27a000"}, - {file = "zope.interface-6.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2606955a06c6852a6cff4abeca38346ed01e83f11e960caa9a821b3626a4467b"}, - {file = "zope.interface-6.2-cp312-cp312-win_amd64.whl", hash = "sha256:ac7c2046d907e3b4e2605a130d162b1b783c170292a11216479bb1deb7cadebe"}, - {file = "zope.interface-6.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:de125151a53ecdb39df3cb3deb9951ed834dd6a110a9e795d985b10bb6db4532"}, - {file = "zope.interface-6.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f444de0565db46d26c9fa931ca14f497900a295bd5eba480fc3fad25af8c763e"}, - {file = "zope.interface-6.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2fefad268ff5c5b314794e27e359e48aeb9c8bb2cbb5748a071757a56f6bb8f"}, - {file = "zope.interface-6.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:97785604824981ec8c81850dd25c8071d5ce04717a34296eeac771231fbdd5cd"}, - {file = "zope.interface-6.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7b2bed4eea047a949296e618552d3fed00632dc1b795ee430289bdd0e3717f3"}, - {file = "zope.interface-6.2-cp38-cp38-win_amd64.whl", hash = "sha256:d54f66c511ea01b9ef1d1a57420a93fbb9d48a08ec239f7d9c581092033156d0"}, - {file = "zope.interface-6.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5ee9789a20b0081dc469f65ff6c5007e67a940d5541419ca03ef20c6213dd099"}, - {file = "zope.interface-6.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af27b3fe5b6bf9cd01b8e1c5ddea0a0d0a1b8c37dc1c7452f1e90bf817539c6d"}, - {file = "zope.interface-6.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4bce517b85f5debe07b186fc7102b332676760f2e0c92b7185dd49c138734b70"}, - {file = "zope.interface-6.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ae9793f114cee5c464cc0b821ae4d36e1eba961542c6086f391a61aee167b6f"}, - {file = "zope.interface-6.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e87698e2fea5ca2f0a99dff0a64ce8110ea857b640de536c76d92aaa2a91ff3a"}, - {file = "zope.interface-6.2-cp39-cp39-win_amd64.whl", hash = "sha256:b66335bbdbb4c004c25ae01cc4a54fd199afbc1fd164233813c6d3c2293bb7e1"}, - {file = "zope.interface-6.2.tar.gz", hash = "sha256:3b6c62813c63c543a06394a636978b22dffa8c5410affc9331ce6cdb5bfa8565"}, + {file = "zope.interface-6.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f32010ffb87759c6a3ad1c65ed4d2e38e51f6b430a1ca11cee901ec2b42e021"}, + {file = "zope.interface-6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e78a183a3c2f555c2ad6aaa1ab572d1c435ba42f1dc3a7e8c82982306a19b785"}, + {file = "zope.interface-6.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa0491a9f154cf8519a02026dc85a416192f4cb1efbbf32db4a173ba28b289a"}, + {file = "zope.interface-6.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62e32f02b3f26204d9c02c3539c802afc3eefb19d601a0987836ed126efb1f21"}, + {file = "zope.interface-6.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c40df4aea777be321b7e68facb901bc67317e94b65d9ab20fb96e0eb3c0b60a1"}, + {file = "zope.interface-6.3-cp310-cp310-win_amd64.whl", hash = "sha256:46034be614d1f75f06e7dcfefba21d609b16b38c21fc912b01a99cb29e58febb"}, + {file = "zope.interface-6.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:600101f43a7582d5b9504a7c629a1185a849ce65e60fca0f6968dfc4b76b6d39"}, + {file = "zope.interface-6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d6b229f5e1a6375f206455cc0a63a8e502ed190fe7eb15e94a312dc69d40299"}, + {file = "zope.interface-6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10cde8dc6b2fd6a1d0b5ca4be820063e46ddba417ab82bcf55afe2227337b130"}, + {file = "zope.interface-6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40aa8c8e964d47d713b226c5baf5f13cdf3a3169c7a2653163b17ff2e2334d10"}, + {file = "zope.interface-6.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d165d7774d558ea971cb867739fb334faf68fc4756a784e689e11efa3becd59e"}, + {file = "zope.interface-6.3-cp311-cp311-win_amd64.whl", hash = "sha256:69dedb790530c7ca5345899a1b4cb837cc53ba669051ea51e8c18f82f9389061"}, + {file = "zope.interface-6.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8d407e0fd8015f6d5dfad481309638e1968d70e6644e0753f229154667dd6cd5"}, + {file = "zope.interface-6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:72d5efecad16c619a97744a4f0b67ce1bcc88115aa82fcf1dc5be9bb403bcc0b"}, + {file = "zope.interface-6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:567d54c06306f9c5b6826190628d66753b9f2b0422f4c02d7c6d2b97ebf0a24e"}, + {file = "zope.interface-6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:483e118b1e075f1819b3c6ace082b9d7d3a6a5eb14b2b375f1b80a0868117920"}, + {file = "zope.interface-6.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb78c12c1ad3a20c0d981a043d133299117b6854f2e14893b156979ed4e1d2c"}, + {file = "zope.interface-6.3-cp312-cp312-win_amd64.whl", hash = "sha256:ad4524289d8dbd6fb5aa17aedb18f5643e7d48358f42c007a5ee51a2afc2a7c5"}, + {file = "zope.interface-6.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:187f7900b63845dcdef1be320a523dbbdba94d89cae570edc2781eb55f8c2f86"}, + {file = "zope.interface-6.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a058e6cf8d68a5a19cb5449f42a404f0d6c2778b897e6ce8fadda9cea308b1b0"}, + {file = "zope.interface-6.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8fa0fb05083a1a4216b4b881fdefa71c5d9a106e9b094cd4399af6b52873e91"}, + {file = "zope.interface-6.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:26c9a37fb395a703e39b11b00b9e921c48f82b6e32cc5851ad5d0618cd8876b5"}, + {file = "zope.interface-6.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b0c4c90e5eefca2c3e045d9f9ed9f1e2cdbe70eb906bff6b247e17119ad89a1"}, + {file = "zope.interface-6.3-cp38-cp38-win_amd64.whl", hash = "sha256:5683aa8f2639016fd2b421df44301f10820e28a9b96382a6e438e5c6427253af"}, + {file = "zope.interface-6.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2c3cfb272bcb83650e6695d49ae0d14dd06dc694789a3d929f23758557a23d92"}, + {file = "zope.interface-6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:01a0b3dd012f584afcf03ed814bce0fc40ed10e47396578621509ac031be98bf"}, + {file = "zope.interface-6.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4137025731e824eee8d263b20682b28a0bdc0508de9c11d6c6be54163e5b7c83"}, + {file = "zope.interface-6.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c8731596198198746f7ce2a4487a0edcbc9ea5e5918f0ab23c4859bce56055c"}, + {file = "zope.interface-6.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf34840e102d1d0b2d39b1465918d90b312b1119552cebb61a242c42079817b9"}, + {file = "zope.interface-6.3-cp39-cp39-win_amd64.whl", hash = "sha256:a1adc14a2a9d5e95f76df625a9b39f4709267a483962a572e3f3001ef90ea6e6"}, + {file = "zope.interface-6.3.tar.gz", hash = "sha256:f83d6b4b22262d9a826c3bd4b2fbfafe1d0000f085ef8e44cd1328eea274ae6a"}, ] diff --git a/pyproject.toml b/pyproject.toml index f098c14923..54e17742df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,10 @@ [project] authors = [ - {name = "Cody Fincher", email = "cody.fincher@gmail.com"}, - {name = "Jacob Coffee", email = "jacob@z7x.org"}, - {name = "Janek NouvertnΓ©", email = "provinzkraut@posteo.de"}, + {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.github@proton.me"}, + {name = "Peter Schutt", email = "peter@litestar.dev"}, ] classifiers = [ "Development Status :: 5 - Production/Stable", @@ -52,7 +52,7 @@ 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 = "provinzkraut@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"} @@ -60,7 +60,7 @@ maintainers = [ name = "litestar" readme = "README.md" requires-python = ">=3.8,<4.0" -version = "2.8.2" +version = "2.9.0" [project.urls] Blog = "https://blog.litestar.dev" @@ -92,7 +92,7 @@ picologging = ["picologging"] prometheus = ["prometheus-client"] pydantic = ["pydantic", "email-validator", "pydantic-extra-types"] redis = ["redis[hiredis]>=4.4.4"] -sqlalchemy = ["advanced-alchemy>=0.2.2,<0.9.0"] +sqlalchemy = ["advanced-alchemy>=0.2.2"] standard = ["jinja2", "jsbeautifier", "uvicorn[standard]", "uvloop>=0.18.0; sys_platform != 'win32'", "fast-query-parsers>=1.0.2"] structlog = ["structlog"] @@ -112,7 +112,17 @@ include = [ ] [tool.pdm] -ignore_package_warnings = ["sphinx", "slotscheck"] +ignore_package_warnings = [ + "alabaster", + "sphinxcontrib-*", + "sphinx-*", + "sphinx", + "pre-commit", + "pydata-sphinx-*", + "slotscheck", + "autobahn", + "accessible-pygments" +] [tool.pdm.dev-dependencies] dev = [ @@ -389,6 +399,7 @@ known-first-party = ["litestar", "tests", "examples"] "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"] "litestar/_openapi/schema_generation/schema.py" = ["C901"] "litestar/exceptions/*.*" = ["N818"] "litestar/handlers/**/*.*" = ["N801"] diff --git a/tests/conftest.py b/tests/conftest.py index ebbac09e70..fd93898882 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,6 +4,7 @@ import logging import os import random +import shutil import string import sys from datetime import datetime @@ -92,6 +93,18 @@ def file_store(tmp_path: Path) -> FileStore: return FileStore(path=tmp_path) +@pytest.fixture() +def file_store_create_directories(tmp_path: Path) -> FileStore: + path = tmp_path / "subdir1" / "subdir2" + return FileStore(path=path, create_directories=True) + + +@pytest.fixture() +def file_store_create_directories_flag_false(tmp_path: Path) -> FileStore: + shutil.rmtree(tmp_path, ignore_errors=True) # in case the path was already created by different tests - we clean it + return FileStore(path=tmp_path.joinpath("subdir"), create_directories=False) + + @pytest.fixture( params=[pytest.param("redis_store", marks=pytest.mark.xdist_group("redis")), "memory_store", "file_store"] ) diff --git a/tests/e2e/test_life_cycle_hooks/test_after_response.py b/tests/e2e/test_life_cycle_hooks/test_after_response.py index 39400c0b27..c9a7554d82 100644 --- a/tests/e2e/test_life_cycle_hooks/test_after_response.py +++ b/tests/e2e/test_life_cycle_hooks/test_after_response.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Dict +from unittest.mock import MagicMock, call import pytest @@ -6,44 +6,35 @@ from litestar.status_codes import HTTP_200_OK from litestar.testing import create_test_client -state: Dict[str, str] = {} -if TYPE_CHECKING: - from litestar.types import AfterResponseHookHandler +@pytest.mark.parametrize("sync", [True, False]) +@pytest.mark.parametrize("layer", ["app", "router", "controller", "handler"]) +def test_after_response_resolution(layer: str, sync: bool) -> None: + mock = MagicMock() + if sync: -def create_sync_test_handler(msg: str) -> "AfterResponseHookHandler": - def handler(_: Request) -> None: - state["msg"] = msg + def handler(_: Request) -> None: # pyright: ignore + mock(layer) - return handler + else: + async def handler(_: Request) -> None: # type: ignore[misc] + mock(layer) -def create_async_test_handler(msg: str) -> "AfterResponseHookHandler": - async def handler(_: Request) -> None: - state["msg"] = msg + class MyController(Controller): + path = "/controller" + after_response = handler if layer == "controller" else None - return handler + @get("/", after_response=handler if layer == "handler" else None) + def my_handler(self) -> None: + return None + router = Router( + path="/router", route_handlers=[MyController], after_response=handler if layer == "router" else None + ) -@pytest.mark.parametrize("layer", ["app", "router", "controller", "handler"]) -def test_after_response_resolution(layer: str) -> None: - for handler in (create_sync_test_handler(layer), create_async_test_handler(layer)): - state.pop("msg", None) - - class MyController(Controller): - path = "/controller" - after_response = handler if layer == "controller" else None - - @get("/", after_response=handler if layer == "handler" else None) - def my_handler(self) -> None: - return None - - router = Router( - path="/router", route_handlers=[MyController], after_response=handler if layer == "router" else None - ) - - with create_test_client(route_handlers=[router], after_response=handler if layer == "app" else None) as client: - response = client.get("/router/controller/") - assert response.status_code == HTTP_200_OK - assert state["msg"] == layer + with create_test_client(route_handlers=[router], after_response=handler if layer == "app" else None) as client: + response = client.get("/router/controller/") + assert response.status_code == HTTP_200_OK + assert all(c == call(layer) for c in mock.call_args_list) diff --git a/tests/e2e/test_middleware/test_logging_middleware_with_multi_body_response.py b/tests/e2e/test_middleware/test_logging_middleware_with_multi_body_response.py new file mode 100644 index 0000000000..0b9c2ef2e3 --- /dev/null +++ b/tests/e2e/test_middleware/test_logging_middleware_with_multi_body_response.py @@ -0,0 +1,30 @@ +from litestar import asgi +from litestar.middleware.logging import LoggingMiddlewareConfig +from litestar.testing import create_async_test_client +from litestar.types.asgi_types import Receive, Scope, Send + + +@asgi("/") +async def asgi_app(scope: Scope, receive: Receive, send: Send) -> None: + await send( + { + "type": "http.response.start", + "status": 200, + "headers": [ + (b"content-type", b"text/event-stream"), + (b"cache-control", b"no-cache"), + (b"connection", b"keep-alive"), + ], + } + ) + + # send two bodies + await send({"type": "http.response.body", "body": b"data: 1\n", "more_body": True}) + await send({"type": "http.response.body", "body": b"data: 2\n", "more_body": False}) + + +async def test_app() -> None: + async with create_async_test_client(asgi_app, middleware=[LoggingMiddlewareConfig().middleware]) as client: + response = await client.get("/") + assert response.status_code == 200 + assert response.text == "data: 1\ndata: 2\n" diff --git a/tests/e2e/test_pydantic.py b/tests/e2e/test_pydantic.py index ad95441ae6..ba9598b891 100644 --- a/tests/e2e/test_pydantic.py +++ b/tests/e2e/test_pydantic.py @@ -1,6 +1,7 @@ import pydantic +from pydantic import v1 as pydantic_v1 -from litestar import get +from litestar import get, post from litestar.testing import create_test_client @@ -22,3 +23,80 @@ def handler_v2() -> ModelV2: with create_test_client([handler_v1, handler_v2]) as client: assert client.get("/v1").json() == {"foo": "bar"} assert client.get("/v2").json() == {"foo": "bar"} + + +def test_pydantic_v1_model_with_field_default() -> None: + # https://github.com/litestar-org/litestar/issues/3471 + + class TestDto(pydantic_v1.BaseModel): + test_str: str = pydantic_v1.Field(default="some_default", max_length=100) + + @post(path="/test") + async def test(data: TestDto) -> str: + return "success" + + with create_test_client(route_handlers=[test]) as client: + response = client.get("/schema/openapi.json") + assert response.status_code == 200 + assert response.json() == { + "components": { + "schemas": { + "test_pydantic_v1_model_with_field_default.TestDto": { + "properties": {"test_str": {"default": "some_default", "maxLength": 100, "type": "string"}}, + "required": [], + "title": "TestDto", + "type": "object", + } + } + }, + "info": {"title": "Litestar API", "version": "1.0.0"}, + "openapi": "3.1.0", + "paths": { + "/test": { + "post": { + "deprecated": False, + "operationId": "TestTest", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/test_pydantic_v1_model_with_field_default.TestDto" + } + } + }, + "required": True, + }, + "responses": { + "201": { + "content": {"text/plain": {"schema": {"type": "string"}}}, + "description": "Document " "created, " "URL " "follows", + "headers": {}, + }, + "400": { + "content": { + "application/json": { + "schema": { + "description": "Validation " "Exception", + "examples": [{"detail": "Bad " "Request", "extra": {}, "status_code": 400}], + "properties": { + "detail": {"type": "string"}, + "extra": { + "additionalProperties": {}, + "type": ["null", "object", "array"], + }, + "status_code": {"type": "integer"}, + }, + "required": ["detail", "status_code"], + "type": "object", + } + } + }, + "description": "Bad " "request " "syntax or " "unsupported " "method", + }, + }, + "summary": "Test", + } + } + }, + "servers": [{"url": "/"}], + } diff --git a/tests/e2e/test_router_registration.py b/tests/e2e/test_router_registration.py index be4858f66a..357411108f 100644 --- a/tests/e2e/test_router_registration.py +++ b/tests/e2e/test_router_registration.py @@ -14,7 +14,9 @@ put, websocket, ) -from litestar import route as route_decorator +from litestar import ( + route as route_decorator, +) from litestar.exceptions import ImproperlyConfiguredException from litestar.routes import HTTPRoute diff --git a/tests/e2e/test_routing/test_path_resolution.py b/tests/e2e/test_routing/test_path_resolution.py index 070bf20c55..4ffdc004a8 100644 --- a/tests/e2e/test_routing/test_path_resolution.py +++ b/tests/e2e/test_routing/test_path_resolution.py @@ -1,5 +1,5 @@ from pathlib import Path -from typing import Any, Callable, List, Optional, Type +from typing import Any, Callable, List, Optional import httpx import pytest @@ -75,29 +75,27 @@ def mixed_params(path_param: int, value: int) -> str: @pytest.mark.parametrize( - "decorator, test_path, decorator_path, delete_handler", + "test_path, decorator_path, delete_handler", [ - (get, "", "/something", None), - (get, "/", "/something", None), - (get, "", "/", None), - (get, "/", "/", None), - (get, "", "", None), - (get, "/", "", None), - (get, "", "/something", root_delete_handler), - (get, "/", "/something", root_delete_handler), - (get, "", "/", root_delete_handler), - (get, "/", "/", root_delete_handler), - (get, "", "", root_delete_handler), - (get, "/", "", root_delete_handler), + ("", "/something", None), + ("/", "/something", None), + ("", "/", None), + ("/", "/", None), + ("", "", None), + ("/", "", None), + ("", "/something", root_delete_handler), + ("/", "/something", root_delete_handler), + ("", "/", root_delete_handler), + ("/", "/", root_delete_handler), + ("", "", root_delete_handler), + ("/", "", root_delete_handler), ], ) -def test_root_route_handler( - decorator: Type[get], test_path: str, decorator_path: str, delete_handler: Optional[Callable] -) -> None: +def test_root_route_handler(test_path: str, decorator_path: str, delete_handler: Optional[Callable]) -> None: class MyController(Controller): path = test_path - @decorator(path=decorator_path) + @get(path=decorator_path) def test_method(self) -> str: return "hello" @@ -360,3 +358,51 @@ async def pathfinder(path: Optional[Path] = None) -> str: assert httpx.get("http://127.0.0.1:9999/").text == "None" assert httpx.get("http://127.0.0.1:9999/something").text == "/something" + + +@pytest.mark.parametrize( + "server_command", + [ + pytest.param(["uvicorn", "app:app", "--port", "9999", "--root-path", "/test"], id="uvicorn"), + pytest.param(["hypercorn", "app:app", "--bind", "127.0.0.1:9999", "--root-path", "/test"], id="hypercorn"), + pytest.param(["daphne", "app:app", "--port", "9999", "--root-path", "/test"], id="daphne"), + ], +) +@pytest.mark.xdist_group("live_server_test") +@pytest.mark.server_integration +def test_no_path_traversal_from_static_directory( + tmp_path: Path, monkeypatch: MonkeyPatch, server_command: List[str], run_server: Callable[[str, List[str]], None] +) -> None: + import http.client + + static = tmp_path / "static" + static.mkdir() + (static / "index.html").write_text("Hello, World!") + + app = """ +from pathlib import Path +from litestar import Litestar +from litestar.static_files import create_static_files_router +import uvicorn + +app = Litestar( + route_handlers=[ + create_static_files_router(path="/static", directories=["static"]), + ], +) + """ + + def send_request(host: str, port: int, path: str) -> http.client.HTTPResponse: + connection = http.client.HTTPConnection(host, port) + connection.request("GET", path) + resp = connection.getresponse() + connection.close() + return resp + + run_server(app, server_command) + + response = send_request("127.0.0.1", 9999, "/static/index.html") + assert response.status == 200 + + response = send_request("127.0.0.1", 9999, "/static/../app.py") + assert response.status == 404 diff --git a/tests/e2e/test_routing/test_route_indexing.py b/tests/e2e/test_routing/test_route_indexing.py index 952579c3a7..f56c4ecc89 100644 --- a/tests/e2e/test_routing/test_route_indexing.py +++ b/tests/e2e/test_routing/test_route_indexing.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Any, Type +from typing import TYPE_CHECKING, Any import pytest @@ -15,15 +15,15 @@ websocket, ) from litestar.exceptions import ImproperlyConfiguredException -from litestar.handlers.http_handlers import HTTPRouteHandler +from litestar.types import HTTPHandlerDecorator if TYPE_CHECKING: from pathlib import Path @pytest.mark.parametrize("decorator", [get, post, patch, put, delete]) -def test_indexes_handlers(decorator: Type[HTTPRouteHandler]) -> None: - @decorator("/path-one/{param:str}", name="handler-name") # type: ignore[call-arg] +def test_indexes_handlers(decorator: HTTPHandlerDecorator) -> None: + @decorator("/path-one/{param:str}", name="handler-name") def handler() -> None: return None @@ -57,19 +57,19 @@ async def websocket_handler(socket: Any) -> None: @pytest.mark.parametrize("decorator", [get, post, patch, put, delete]) -def test_default_indexes_handlers(decorator: Type[HTTPRouteHandler]) -> None: - @decorator("/handler") # type: ignore[call-arg] +def test_default_indexes_handlers(decorator: HTTPHandlerDecorator) -> None: + @decorator("/handler") def handler() -> None: pass - @decorator("/named_handler", name="named_handler") # type: ignore[call-arg] + @decorator("/named_handler", name="named_handler") def named_handler() -> None: pass class MyController(Controller): path = "/test" - @decorator() # type: ignore[call-arg] + @decorator() def handler(self) -> None: pass @@ -93,12 +93,12 @@ def handler(self) -> None: @pytest.mark.parametrize("decorator", [get, post, patch, put, delete]) -def test_indexes_handlers_with_multiple_paths(decorator: Type[HTTPRouteHandler]) -> None: - @decorator(["/path-one", "/path-one/{param:str}"], name="handler") # type: ignore[call-arg] +def test_indexes_handlers_with_multiple_paths(decorator: HTTPHandlerDecorator) -> None: + @decorator(["/path-one", "/path-one/{param:str}"], name="handler") def handler() -> None: return None - @decorator(["/path-two"], name="handler-two") # type: ignore[call-arg] + @decorator(["/path-two"], name="handler-two") def handler_two() -> None: return None diff --git a/tests/e2e/test_routing/test_route_reverse.py b/tests/e2e/test_routing/test_route_reverse.py index 0a8d914883..32b948cdc2 100644 --- a/tests/e2e/test_routing/test_route_reverse.py +++ b/tests/e2e/test_routing/test_route_reverse.py @@ -1,35 +1,34 @@ from datetime import time -from typing import Type import pytest from litestar import Litestar, Router, delete, get, patch, post, put from litestar.exceptions import NoRouteMatchFoundException -from litestar.handlers.http_handlers import HTTPRouteHandler +from litestar.types import HTTPHandlerDecorator @pytest.mark.parametrize("decorator", [get, post, patch, put, delete]) -def test_route_reverse(decorator: Type[HTTPRouteHandler]) -> None: - @decorator("/path-one/{param:str}", name="handler-name") # type: ignore[call-arg] +def test_route_reverse(decorator: HTTPHandlerDecorator) -> None: + @decorator("/path-one/{param:str}", name="handler-name") def handler() -> None: return None - @decorator("/path-two", name="handler-no-params") # type: ignore[call-arg] + @decorator("/path-two", name="handler-no-params") def handler_no_params() -> None: return None - @decorator("/multiple/{str_param:str}/params/{int_param:int}/", name="multiple-params-handler-name") # type: ignore[call-arg] + @decorator("/multiple/{str_param:str}/params/{int_param:int}/", name="multiple-params-handler-name") def handler2() -> None: return None @decorator( ["/handler3", "/handler3/{str_param:str}/", "/handler3/{str_param:str}/{int_param:int}/"], name="multiple-default-params", - ) # type: ignore[call-arg] + ) def handler3(str_param: str = "default", int_param: int = 0) -> None: return None - @decorator(["/handler4/int/{int_param:int}", "/handler4/str/{str_param:str}"], name="handler4") # type: ignore[call-arg] + @decorator(["/handler4/int/{int_param:int}", "/handler4/str/{str_param:str}"], name="handler4") def handler4(int_param: int = 1, str_param: str = "str") -> None: return None diff --git a/tests/examples/test_contrib/prometheus/test_prometheus_exporter_example.py b/tests/examples/test_contrib/prometheus/test_prometheus_exporter_example.py index 954636977e..3bc46bf3f1 100644 --- a/tests/examples/test_contrib/prometheus/test_prometheus_exporter_example.py +++ b/tests/examples/test_contrib/prometheus/test_prometheus_exporter_example.py @@ -1,5 +1,6 @@ from typing import Any, Dict +import pytest from prometheus_client import REGISTRY from litestar import get @@ -16,25 +17,30 @@ def clear_collectors() -> None: PrometheusMiddleware._metrics = {} -def test_prometheus_exporter_example() -> None: - from docs.examples.contrib.prometheus.using_prometheus_exporter import app +@pytest.mark.parametrize( + "group_path, expected_path", + [ + (True, "/test/{name}"), + (False, "/test/litestar"), + ], +) +def test_prometheus_exporter_example(group_path: bool, expected_path: str) -> None: + from docs.examples.contrib.prometheus.using_prometheus_exporter import create_app + + app = create_app(group_path=group_path) clear_collectors() - @get("/test") - def home() -> Dict[str, Any]: - return {"hello": "world"} + @get("test/{name: str}") + def home(name: str) -> Dict[str, Any]: + return {"hello": name} app.register(home) with TestClient(app) as client: - client.get("/home") + client.get("/test/litestar") metrix_exporter_response = client.get("/metrics") assert metrix_exporter_response.status_code == HTTP_200_OK metrics = metrix_exporter_response.content.decode() - - assert ( - """litestar_requests_in_progress{app_name="litestar",method="GET",path="/metrics",status_code="200"} 1.0""" - in metrics - ) + assert expected_path in metrics diff --git a/tests/examples/test_contrib/test_sqlalchemy/test_sqlalchemy_examples.py b/tests/examples/test_contrib/test_sqlalchemy/test_sqlalchemy_examples.py new file mode 100644 index 0000000000..0aa531bd33 --- /dev/null +++ b/tests/examples/test_contrib/test_sqlalchemy/test_sqlalchemy_examples.py @@ -0,0 +1,14 @@ +import pytest + +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 + + with TestClient(app) as client: + response = client.get("/authors") + assert response.status_code == 200 + assert len(response.json()) > 0 diff --git a/tests/examples/test_data_transfer_objects/test_factory/__init__.py b/tests/examples/test_data_transfer_objects/test_factory/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/examples/test_data_transfer_objects/test_factory/test_dto_data_problem_statement.py b/tests/examples/test_data_transfer_objects/test_factory/test_dto_data_problem_statement.py new file mode 100644 index 0000000000..209ca901c3 --- /dev/null +++ b/tests/examples/test_data_transfer_objects/test_factory/test_dto_data_problem_statement.py @@ -0,0 +1,14 @@ +from unittest.mock import ANY + +from litestar.status_codes import HTTP_201_CREATED +from litestar.testing.client import TestClient + + +def test_create_user(user_data: dict) -> None: + from docs.examples.data_transfer_objects.factory.dto_data_problem_statement import app + + with TestClient(app=app) as client: + response = client.post("/users", json=user_data) + + assert response.status_code == HTTP_201_CREATED + assert response.json() == {"id": ANY, "name": "Mr Sunglass", "email": "mr.sunglass@example.com", "age": 30} diff --git a/tests/examples/test_data_transfer_objects/test_factory/test_dto_data_usage.py b/tests/examples/test_data_transfer_objects/test_factory/test_dto_data_usage.py new file mode 100644 index 0000000000..02b0bcf992 --- /dev/null +++ b/tests/examples/test_data_transfer_objects/test_factory/test_dto_data_usage.py @@ -0,0 +1,13 @@ +from unittest.mock import ANY + +from litestar.testing import TestClient + + +def test_create_user(user_data) -> None: + from docs.examples.data_transfer_objects.factory.dto_data_usage import app + + with TestClient(app) as client: + response = client.post("/users", json=user_data) + + assert response.status_code == 201 + assert response.json() == {"id": ANY, "name": "Mr Sunglass", "email": "mr.sunglass@example.com", "age": 30} diff --git a/tests/examples/test_data_transfer_objects/test_factory/test_leading_underscore_private.py b/tests/examples/test_data_transfer_objects/test_factory/test_leading_underscore_private.py new file mode 100644 index 0000000000..d6803d220a --- /dev/null +++ b/tests/examples/test_data_transfer_objects/test_factory/test_leading_underscore_private.py @@ -0,0 +1,12 @@ +from litestar.status_codes import HTTP_201_CREATED +from litestar.testing.client import TestClient + + +def test_create_underscored_value() -> None: + from docs.examples.data_transfer_objects.factory.leading_underscore_private import app + + with TestClient(app=app) as client: + response = client.post("/", json={"this_will": "stay", "_this_will": "go_away!"}) + + assert response.status_code == HTTP_201_CREATED + assert response.json() == {"this_will": "stay"} diff --git a/tests/examples/test_data_transfer_objects/test_factory/test_leading_underscore_private_override.py b/tests/examples/test_data_transfer_objects/test_factory/test_leading_underscore_private_override.py new file mode 100644 index 0000000000..8b98033920 --- /dev/null +++ b/tests/examples/test_data_transfer_objects/test_factory/test_leading_underscore_private_override.py @@ -0,0 +1,12 @@ +from litestar.status_codes import HTTP_201_CREATED +from litestar.testing.client import TestClient + + +def test_create_underscored_field() -> None: + from docs.examples.data_transfer_objects.factory.leading_underscore_private_override import app + + with TestClient(app=app) as client: + response = client.post("/", json={"this_will": "stay", "_this_will": "not_go_away!"}) + + assert response.status_code == HTTP_201_CREATED + assert response.json() == {"this_will": "stay", "_this_will": "not_go_away!"} diff --git a/tests/examples/test_data_transfer_objects/test_factory/test_type_checking.py b/tests/examples/test_data_transfer_objects/test_factory/test_type_checking.py new file mode 100644 index 0000000000..36e80c95d8 --- /dev/null +++ b/tests/examples/test_data_transfer_objects/test_factory/test_type_checking.py @@ -0,0 +1,8 @@ +import pytest + +from litestar.exceptions.dto_exceptions import InvalidAnnotationException + + +def test_should_raise_error_on_route_registration() -> None: + with pytest.raises(InvalidAnnotationException): + from docs.examples.data_transfer_objects.factory.type_checking import app # noqa: F401 diff --git a/tests/examples/test_data_transfer_objects/test_overriding_implicit_return_dto.py b/tests/examples/test_data_transfer_objects/test_overriding_implicit_return_dto.py new file mode 100644 index 0000000000..921707c621 --- /dev/null +++ b/tests/examples/test_data_transfer_objects/test_overriding_implicit_return_dto.py @@ -0,0 +1,12 @@ +from litestar.status_codes import HTTP_201_CREATED +from litestar.testing.client import TestClient + + +def test_create_user(user_data: dict) -> None: + from docs.examples.data_transfer_objects.overriding_implicit_return_dto import app + + with TestClient(app=app) as client: + response = client.post("/", json=user_data) + + assert response.status_code == HTTP_201_CREATED + assert response.content == b"Mr Sunglass" diff --git a/tests/examples/test_dto/test_example_apps.py b/tests/examples/test_dto/test_example_apps.py index 3ad9573279..353c976590 100644 --- a/tests/examples/test_dto/test_example_apps.py +++ b/tests/examples/test_dto/test_example_apps.py @@ -1,27 +1,8 @@ from __future__ import annotations -from unittest.mock import ANY - from litestar.testing import TestClient -def test_dto_data_problem_statement_app() -> None: - from docs.examples.data_transfer_objects.factory.dto_data_problem_statement import app - - with TestClient(app) as client: - response = client.post("/person", json={"name": "John", "age": 30}) - assert response.status_code == 500 - - -def test_dto_data_usage_app() -> None: - from docs.examples.data_transfer_objects.factory.dto_data_usage import app - - with TestClient(app) as client: - response = client.post("/person", json={"name": "John", "age": 30}) - assert response.status_code == 201 - assert response.json() == {"id": ANY, "name": "John", "age": 30} - - def test_dto_data_nested_data_create_instance_app() -> None: from docs.examples.data_transfer_objects.factory.providing_values_for_nested_data import app diff --git a/tests/examples/test_responses/test_sse_responses.py b/tests/examples/test_responses/test_sse_responses.py index 2309d7424e..9f92d668f1 100644 --- a/tests/examples/test_responses/test_sse_responses.py +++ b/tests/examples/test_responses/test_sse_responses.py @@ -8,6 +8,4 @@ async def test_sse_responses_example() -> None: async with AsyncTestClient(app=app) as client: async with aconnect_sse(client, "GET", f"{client.base_url}/count") as event_source: events = [sse async for sse in event_source.aiter_sse()] - assert len(events) == 10 - assert all(e.event == "message" for e in events) - assert all(e.data == str(i) for i, e in enumerate(events, 1)) + assert len(events) == 50 diff --git a/tests/unit/test_asgi/test_routing_trie/test_traversal.py b/tests/unit/test_asgi/test_routing_trie/test_traversal.py new file mode 100644 index 0000000000..a92995750d --- /dev/null +++ b/tests/unit/test_asgi/test_routing_trie/test_traversal.py @@ -0,0 +1,93 @@ +from typing import Any + +from litestar import Router, asgi, get +from litestar.response.base import ASGIResponse +from litestar.status_codes import HTTP_404_NOT_FOUND +from litestar.testing import create_test_client + + +def test_parse_path_to_route_mounted_app_path_root() -> None: + # test that paths are correctly dispatched to handlers when mounting an app + # and other handlers to root path / + + @asgi("/foobar", is_mount=True) + async def mounted_handler(scope: Any, receive: Any, send: Any) -> None: + response = ASGIResponse(body="mounted") + await response(scope, receive, send) + + @get("/{number:int}/foobar/") + async def parametrized_handler() -> str: + return "parametrized" + + @get("/static/foobar/") + async def static_handler() -> str: + return "static" + + with create_test_client( + [ + mounted_handler, + parametrized_handler, + static_handler, + ] + ) as client: + response = client.get("/foobar") + assert response.text == "mounted" + + response = client.get("/foobar/123/") + assert response.text == "mounted" + + response = client.get("/123/foobar/") + assert response.text == "parametrized" + + response = client.get("/static/foobar/") + assert response.text == "static" + + response = client.get("/unknown/foobar/") + assert response.status_code == HTTP_404_NOT_FOUND + + +def test_parse_path_to_route_mounted_app_path_router() -> None: + # test that paths are correctly dispatched to handlers when mounting an app + # and other handlers inside subrouter + + @asgi("/foobar", is_mount=True) + async def mounted_handler(scope: Any, receive: Any, send: Any) -> None: + response = ASGIResponse(body="mounted") + await response(scope, receive, send) + + @get("/{number:int}/foobar/") + async def parametrized_handler() -> str: + return "parametrized" + + @get("/static/foobar/") + async def static_handler() -> str: + return "static" + + sub_router = Router( + path="/sub", + route_handlers=[ + mounted_handler, + parametrized_handler, + static_handler, + ], + ) + base_router = Router(path="/base", route_handlers=[sub_router]) + + with create_test_client([base_router]) as client: + response = client.get("/foobar") + assert response.status_code == HTTP_404_NOT_FOUND + + response = client.get("/base/sub/foobar") + assert response.text == "mounted" + + response = client.get("/base/sub/foobar/123/") + assert response.text == "mounted" + + response = client.get("/base/sub/123/foobar/") + assert response.text == "parametrized" + + response = client.get("/base/sub/static/foobar/") + assert response.text == "static" + + response = client.get("/base/sub/unknown/foobar/") + assert response.status_code == HTTP_404_NOT_FOUND diff --git a/tests/unit/test_cli/test_cli.py b/tests/unit/test_cli/test_cli.py index cec90b7698..09af3a8fcf 100644 --- a/tests/unit/test_cli/test_cli.py +++ b/tests/unit/test_cli/test_cli.py @@ -89,4 +89,4 @@ def custom_command(app: Litestar) -> None: result = runner.invoke(cli_command, f"--app={app_file.stem}:app custom-group custom-command") assert result.exit_code == 0 - mock_command_callback.assert_called_once_with() + mock_command_callback.assert_called_once() diff --git a/tests/unit/test_connection/test_websocket.py b/tests/unit/test_connection/test_websocket.py index 840d64b0fb..5e7a2e2d38 100644 --- a/tests/unit/test_connection/test_websocket.py +++ b/tests/unit/test_connection/test_websocket.py @@ -92,7 +92,18 @@ async def handler(socket: WebSocket) -> None: await socket.close() with create_test_client(handler).websocket_connect("/123?a=abc") as ws: - assert ws.receive_json() == {"url": "ws://testserver/123?a=abc"} + assert ws.receive_json() == {"url": "ws://testserver.local/123?a=abc"} + + +def test_websocket_url_respects_custom_base_url() -> None: + @websocket("/123") + async def handler(socket: WebSocket) -> None: + await socket.accept() + await socket.send_json({"url": str(socket.url)}) + await socket.close() + + with create_test_client(handler, base_url="http://example.org").websocket_connect("/123?a=abc") as ws: + assert ws.receive_json() == {"url": "ws://example.org/123?a=abc"} def test_websocket_binary_json() -> None: @@ -133,7 +144,7 @@ async def handler(socket: WebSocket) -> None: "accept": "*/*", "accept-encoding": "gzip, deflate, br", "connection": "upgrade", - "host": "testserver", + "host": "testserver.local", "user-agent": "testclient", "sec-websocket-key": "testserver==", "sec-websocket-version": "13", diff --git a/tests/unit/test_contrib/test_opentelemetry.py b/tests/unit/test_contrib/test_opentelemetry.py index ef16140b5e..4d4d611887 100644 --- a/tests/unit/test_contrib/test_opentelemetry.py +++ b/tests/unit/test_contrib/test_opentelemetry.py @@ -113,11 +113,11 @@ async def handler(socket: "WebSocket") -> None: assert dict(fourth_span.attributes) == {"type": "websocket.close"} # type: ignore[arg-type] assert dict(fifth_span.attributes) == { # type: ignore[arg-type] "http.scheme": "ws", - "http.host": "testserver", + "http.host": "testserver.local", "net.host.port": 80, "http.target": "/", - "http.url": "ws://testserver/", - "http.server_name": "testserver", + "http.url": "ws://testserver.local/", + "http.server_name": "testserver.local", "http.user_agent": "testclient", "net.peer.ip": "testclient", "net.peer.port": 50000, diff --git a/tests/unit/test_contrib/test_pydantic/test_integration.py b/tests/unit/test_contrib/test_pydantic/test_integration.py index 7fccb3b522..9cc9d285c3 100644 --- a/tests/unit/test_contrib/test_pydantic/test_integration.py +++ b/tests/unit/test_contrib/test_pydantic/test_integration.py @@ -1,4 +1,5 @@ from typing import Any, Dict, List +from unittest.mock import ANY import pydantic as pydantic_v2 import pytest @@ -135,7 +136,7 @@ async def create_user(data: User) -> User: "msg": "Value error, user id must be greater than 0", "input": -1, "ctx": {"error": "ValueError"}, - "url": "https://errors.pydantic.dev/2.6/v/value_error", + "url": ANY, } ] diff --git a/tests/unit/test_contrib/test_pydantic/test_plugin_serialization.py b/tests/unit/test_contrib/test_pydantic/test_plugin_serialization.py index 435cf7b8e2..528820e709 100644 --- a/tests/unit/test_contrib/test_pydantic/test_plugin_serialization.py +++ b/tests/unit/test_contrib/test_pydantic/test_plugin_serialization.py @@ -27,6 +27,8 @@ from . import PydanticVersion +TODAY = datetime.date.today() + class CustomStr(str): pass @@ -84,7 +86,7 @@ class Config: constr: pydantic_v1.constr(min_length=1) # type: ignore[valid-type] conbytes: pydantic_v1.conbytes(min_length=1) # type: ignore[valid-type] - condate: pydantic_v1.condate(ge=datetime.date.today()) # type: ignore[valid-type] + condate: pydantic_v1.condate(ge=TODAY) # type: ignore[valid-type] condecimal: pydantic_v1.condecimal(ge=Decimal("1")) # type: ignore[valid-type] confloat: pydantic_v1.confloat(ge=0) # type: ignore[valid-type] @@ -112,7 +114,7 @@ class ModelV2(pydantic_v2.BaseModel): constr: pydantic_v2.constr(min_length=1) # type: ignore[valid-type] conbytes: pydantic_v2.conbytes(min_length=1) # type: ignore[valid-type] - condate: pydantic_v2.condate(ge=datetime.date.today()) # type: ignore[valid-type] + condate: pydantic_v2.condate(ge=TODAY) # type: ignore[valid-type] condecimal: pydantic_v2.condecimal(ge=Decimal("1")) # type: ignore[valid-type] confloat: pydantic_v2.confloat(ge=0) # type: ignore[valid-type] @@ -144,7 +146,7 @@ def model(pydantic_version: PydanticVersion) -> ModelV1 | ModelV2: payment_card_number=pydantic_v1.PaymentCardNumber("4000000000000002"), constr="hello", conbytes=b"hello", - condate=datetime.date.today(), + condate=TODAY, condecimal=Decimal("3.14"), confloat=1.0, conset={1}, @@ -165,7 +167,7 @@ def model(pydantic_version: PydanticVersion) -> ModelV1 | ModelV2: payment_card_number=pydantic_v2.PaymentCardNumber("4000000000000002"), constr="hello", conbytes=b"hello", - condate=datetime.date.today(), + condate=TODAY, condecimal=Decimal("3.14"), confloat=1.0, conset={1}, @@ -190,7 +192,7 @@ def model(pydantic_version: PydanticVersion) -> ModelV1 | ModelV2: ("payment_card_number", "4000000000000002"), ("constr", "hello"), ("conbytes", b"hello"), - ("condate", datetime.date.today().isoformat()), + ("condate", TODAY.isoformat()), ("condecimal", 3.14), ("conset", {1}), ("confrozenset", frozenset([1])), diff --git a/tests/unit/test_controller.py b/tests/unit/test_controller.py index e049608f24..cc9b15d2a9 100644 --- a/tests/unit/test_controller.py +++ b/tests/unit/test_controller.py @@ -1,4 +1,4 @@ -from typing import Any, Type, Union +from typing import Any import msgspec import pytest @@ -19,6 +19,7 @@ from litestar.exceptions import ImproperlyConfiguredException from litestar.status_codes import HTTP_200_OK, HTTP_201_CREATED, HTTP_204_NO_CONTENT from litestar.testing import create_test_client +from litestar.types import HTTPHandlerDecorator from tests.models import DataclassPerson, DataclassPersonFactory @@ -40,7 +41,7 @@ ], ) async def test_controller_http_method( - decorator: Union[Type[get], Type[post], Type[put], Type[patch], Type[delete]], + decorator: HTTPHandlerDecorator, http_method: HttpMethod, expected_status_code: int, return_value: Any, @@ -51,7 +52,7 @@ async def test_controller_http_method( class MyController(Controller): path = test_path - @decorator() # type: ignore[misc] + @decorator() def test_method(self) -> return_annotation: return return_value diff --git a/tests/unit/test_dto/test_factory/test_backends/test_backends.py b/tests/unit/test_dto/test_factory/test_backends/test_backends.py index fe92cd9c87..6736ff3cf8 100644 --- a/tests/unit/test_dto/test_factory/test_backends/test_backends.py +++ b/tests/unit/test_dto/test_factory/test_backends/test_backends.py @@ -3,7 +3,7 @@ from dataclasses import dataclass, field from types import ModuleType -from typing import TYPE_CHECKING, Callable, List, Optional +from typing import TYPE_CHECKING, Callable, Dict, List, Optional from unittest.mock import MagicMock import pytest @@ -40,6 +40,7 @@ class DC: a: int nested: NestedDC nested_list: List[NestedDC] + nested_mapping: Dict[str, NestedDC] b: str = field(default="b") c: List[int] = field(default_factory=list) optional: Optional[str] = None @@ -51,13 +52,20 @@ class DC: "c": [], "nested": {"a": 1, "b": "two"}, "nested_list": [{"a": 1, "b": "two"}], + "nested_mapping": {"a": {"a": 1, "b": "two"}}, "optional": None, } -RAW = b'{"a":1,"nested":{"a":1,"b":"two"},"nested_list":[{"a":1,"b":"two"}],"b":"b","c":[],"optional":null}' -COLLECTION_RAW = ( - b'[{"a":1,"nested":{"a":1,"b":"two"},"nested_list":[{"a":1,"b":"two"}],"b":"b","c":[],"optional":null}]' +RAW = b'{"a":1,"nested":{"a":1,"b":"two"},"nested_list":[{"a":1,"b":"two"}],"nested_mapping":{"a":{"a":1,"b":"two"}},"b":"b","c":[],"optional":null}' +COLLECTION_RAW = b'[{"a":1,"nested":{"a":1,"b":"two"},"nested_list":[{"a":1,"b":"two"}],"nested_mapping":{"a":{"a":1,"b":"two"}},"b":"b","c":[],"optional":null}]' +STRUCTURED = DC( + a=1, + b="b", + c=[], + nested=NestedDC(a=1, b="two"), + nested_list=[NestedDC(a=1, b="two")], + nested_mapping={"a": NestedDC(a=1, b="two")}, + optional=None, ) -STRUCTURED = DC(a=1, b="b", c=[], nested=NestedDC(a=1, b="two"), nested_list=[NestedDC(a=1, b="two")], optional=None) @pytest.fixture(name="dto_factory") @@ -89,7 +97,10 @@ def test_backend_parse_raw_json( wrapper_attribute_name=None, is_data_field=True, handler_id="test", - ).parse_raw(b'{"a":1,"nested":{"a":1,"b":"two"},"nested_list":[{"a":1,"b":"two"}]}', asgi_connection) + ).parse_raw( + b'{"a":1,"nested":{"a":1,"b":"two"},"nested_list":[{"a":1,"b":"two"}],"nested_mapping":{"a":{"a":1,"b":"two"}}}', + asgi_connection, + ) ) == DESTRUCTURED ) @@ -112,7 +123,7 @@ def _handler() -> None: ... is_data_field=True, handler_id="test", ).parse_raw( - b"\x83\xa1a\x01\xa6nested\x82\xa1a\x01\xa1b\xa3two\xabnested_list\x91\x82\xa1a\x01\xa1b\xa3two", + b"\x87\xa1a\x01\xa6nested\x82\xa1a\x01\xa1b\xa3two\xabnested_list\x91\x82\xa1a\x01\xa1b\xa3two\xaenested_mapping\x81\xa1a\x82\xa1a\x01\xa1b\xa3two\xa1b\xa1b\xa1c\x90\xa8optional\xc0", asgi_connection, ) ) diff --git a/tests/unit/test_dto/test_factory/test_integration.py b/tests/unit/test_dto/test_factory/test_integration.py index 21dc1c027f..5f7eafee1e 100644 --- a/tests/unit/test_dto/test_factory/test_integration.py +++ b/tests/unit/test_dto/test_factory/test_integration.py @@ -1003,3 +1003,58 @@ def get_users() -> WithCount[User]: assert not_none(schema.properties).keys() == {"count", "data"} model_schema = openapi.components.schemas["GetUsersUserResponseBody"] assert not_none(model_schema.properties).keys() == {"id", "name"} + + +def test_openapi_schema_for_dto_includes_body_examples(create_module: Callable[[str], ModuleType]) -> None: + module = create_module( + """ +from dataclasses import dataclass +from uuid import UUID + +from typing_extensions import Annotated + +from litestar import Litestar, post +from litestar.dto import DataclassDTO +from litestar.openapi.spec import Example +from litestar.params import Body + + +@dataclass +class Item: + id: UUID + name: str + + +body = Body( + title="Create item", + description="Create a new item.", + examples=[ + Example( + summary="Post is Ok", + value={ + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "Swatch", + }, + ) + ], +) + + +@post() +async def create_item(data: Annotated[Item, body]) -> Item: + return data + + +@post("dto", dto=DataclassDTO[Item]) +async def create_item_with_dto(data: Annotated[Item, body]) -> Item: + return data + + +app = Litestar(route_handlers=[create_item, create_item_with_dto]) +""" + ) + + openapi_schema = module.app.openapi_schema + item_schema = openapi_schema.components.schemas["Item"] + item_with_dto_schema = openapi_schema.components.schemas["CreateItemWithDtoItemRequestBody"] + assert item_schema.examples == item_with_dto_schema.examples diff --git a/tests/unit/test_handlers/test_asgi_handlers/test_handle_asgi.py b/tests/unit/test_handlers/test_asgi_handlers/test_handle_asgi.py index 84d9320c98..1230399cef 100644 --- a/tests/unit/test_handlers/test_asgi_handlers/test_handle_asgi.py +++ b/tests/unit/test_handlers/test_asgi_handlers/test_handle_asgi.py @@ -1,5 +1,6 @@ from litestar import Controller, MediaType, asgi from litestar.enums import ScopeType +from litestar.handlers import ASGIRouteHandler from litestar.response.base import ASGIResponse from litestar.status_codes import HTTP_200_OK from litestar.testing import create_test_client @@ -51,3 +52,14 @@ async def root_asgi_handler( response = client.get("/asgi") assert response.status_code == HTTP_200_OK assert response.text == "/asgi" + + +def test_custom_handler_class() -> None: + class MyHandlerClass(ASGIRouteHandler): + pass + + @asgi("/", handler_class=MyHandlerClass) + async def handler() -> None: + pass + + assert isinstance(handler, MyHandlerClass) diff --git a/tests/unit/test_handlers/test_base_handlers/test_opt.py b/tests/unit/test_handlers/test_base_handlers/test_opt.py index 453eb00889..60558ff2f6 100644 --- a/tests/unit/test_handlers/test_base_handlers/test_opt.py +++ b/tests/unit/test_handlers/test_base_handlers/test_opt.py @@ -17,7 +17,7 @@ if TYPE_CHECKING: from litestar import WebSocket - from litestar.types import Receive, RouteHandlerType, Scope, Send + from litestar.types import AnyCallable, Receive, RouteHandlerType, Scope, Send def regular_handler() -> None: ... @@ -41,9 +41,11 @@ async def socket_handler(socket: "WebSocket") -> None: ... (websocket, socket_handler), ], ) -def test_opt_settings(decorator: "RouteHandlerType", handler: Callable) -> None: +def test_opt_settings( + decorator: Callable[..., Callable[["AnyCallable"], "RouteHandlerType"]], handler: "Callable" +) -> None: base_opt = {"base": 1, "kwarg_value": 0} - result = decorator("/", opt=base_opt, kwarg_value=2)(handler) # type: ignore[arg-type, call-arg] + result = decorator("/", opt=base_opt, kwarg_value=2)(handler) assert result.opt == {"base": 1, "kwarg_value": 2} diff --git a/tests/unit/test_handlers/test_base_handlers/test_validations.py b/tests/unit/test_handlers/test_base_handlers/test_validations.py index a0b168a230..e4f9cc5e06 100644 --- a/tests/unit/test_handlers/test_base_handlers/test_validations.py +++ b/tests/unit/test_handlers/test_base_handlers/test_validations.py @@ -5,14 +5,6 @@ from litestar import Litestar, post from litestar.dto import DTOData from litestar.exceptions import ImproperlyConfiguredException -from litestar.handlers.base import BaseRouteHandler - - -def test_raise_no_fn_validation() -> None: - handler = BaseRouteHandler(path="/") - - with pytest.raises(ImproperlyConfiguredException): - handler.fn def test_dto_data_annotation_with_no_resolved_dto() -> None: diff --git a/tests/unit/test_handlers/test_http_handlers/test_custom_handler_class.py b/tests/unit/test_handlers/test_http_handlers/test_custom_handler_class.py new file mode 100644 index 0000000000..3df1d6954f --- /dev/null +++ b/tests/unit/test_handlers/test_http_handlers/test_custom_handler_class.py @@ -0,0 +1,30 @@ +from typing import Callable + +import pytest + +from litestar.handlers import HTTPRouteHandler +from litestar.handlers.http_handlers import delete, get, patch, post, put, route +from litestar.types import AnyCallable + + +@pytest.mark.parametrize("handler_decorator", [get, put, delete, post, patch]) +def test_custom_handler_class(handler_decorator: Callable[..., Callable[[AnyCallable], HTTPRouteHandler]]) -> None: + class MyHandlerClass(HTTPRouteHandler): + pass + + @handler_decorator("/", handler_class=MyHandlerClass) + async def handler() -> None: + pass + + assert isinstance(handler, MyHandlerClass) + + +def test_custom_handler_class_route() -> None: + class MyHandlerClass(HTTPRouteHandler): + pass + + @route("/", handler_class=MyHandlerClass, http_method="GET") + async def handler() -> None: + pass + + assert isinstance(handler, MyHandlerClass) diff --git a/tests/unit/test_handlers/test_http_handlers/test_defaults.py b/tests/unit/test_handlers/test_http_handlers/test_defaults.py index 06c0387f98..314e131ccc 100644 --- a/tests/unit/test_handlers/test_http_handlers/test_defaults.py +++ b/tests/unit/test_handlers/test_http_handlers/test_defaults.py @@ -37,5 +37,5 @@ ], ) def test_route_handler_default_status_code(http_method: Any, expected_status_code: int) -> None: - route_handler = HTTPRouteHandler(http_method=http_method) + route_handler = HTTPRouteHandler(http_method=http_method, fn=lambda: None) assert route_handler.status_code == expected_status_code diff --git a/tests/unit/test_handlers/test_http_handlers/test_head.py b/tests/unit/test_handlers/test_http_handlers/test_head.py index 8cc2e7b2a5..76cadaacf5 100644 --- a/tests/unit/test_handlers/test_http_handlers/test_head.py +++ b/tests/unit/test_handlers/test_http_handlers/test_head.py @@ -2,7 +2,7 @@ import pytest -from litestar import HttpMethod, Litestar, head +from litestar import Litestar, head from litestar.exceptions import ImproperlyConfiguredException from litestar.response.file import ASGIFileResponse, File from litestar.routes import HTTPRoute @@ -30,16 +30,6 @@ def handler() -> dict: handler.on_registration(Litestar(), HTTPRoute(path="/", route_handlers=[handler])) -def test_head_decorator_raises_validation_error_if_method_is_passed() -> None: - with pytest.raises(ImproperlyConfiguredException): - - @head("/", http_method=HttpMethod.HEAD) - def handler() -> None: - return - - handler.on_registration(Litestar(), HTTPRoute(path="/", route_handlers=[handler])) - - def test_head_decorator_does_not_raise_for_file_response() -> None: @head("/") def handler() -> "File": diff --git a/tests/unit/test_handlers/test_http_handlers/test_kwarg_handling.py b/tests/unit/test_handlers/test_http_handlers/test_kwarg_handling.py index 73ed586b50..326972ecac 100644 --- a/tests/unit/test_handlers/test_http_handlers/test_kwarg_handling.py +++ b/tests/unit/test_handlers/test_http_handlers/test_kwarg_handling.py @@ -4,11 +4,10 @@ from hypothesis import given from hypothesis import strategies as st -from litestar import HttpMethod, MediaType, Response, delete, get, patch, post, put +from litestar import HttpMethod, MediaType, Response from litestar.exceptions import ImproperlyConfiguredException from litestar.handlers.http_handlers import HTTPRouteHandler from litestar.handlers.http_handlers._utils import get_default_status_code -from litestar.status_codes import HTTP_200_OK, HTTP_201_CREATED, HTTP_204_NO_CONTENT from litestar.utils import normalize_path @@ -36,9 +35,9 @@ def test_route_handler_kwarg_handling( ) -> None: if not http_method: with pytest.raises(ImproperlyConfiguredException): - HTTPRouteHandler(http_method=http_method) + HTTPRouteHandler(http_method=http_method, fn=dummy_method) else: - decorator = HTTPRouteHandler( + result = HTTPRouteHandler( http_method=http_method, media_type=media_type, include_in_schema=include_in_schema, @@ -46,8 +45,8 @@ def test_route_handler_kwarg_handling( response_headers=response_headers, status_code=status_code, path=path, + fn=dummy_method, ) - result = decorator(dummy_method) if isinstance(http_method, list): assert all(method in result.http_methods for method in http_method) else: @@ -61,24 +60,3 @@ def test_route_handler_kwarg_handling( else: assert next(iter(result.paths)) == normalize_path(path) assert result.status_code == status_code or get_default_status_code(http_methods=result.http_methods) - - -@pytest.mark.parametrize( - "sub, http_method, expected_status_code", - [ - (post, HttpMethod.POST, HTTP_201_CREATED), - (delete, HttpMethod.DELETE, HTTP_204_NO_CONTENT), - (get, HttpMethod.GET, HTTP_200_OK), - (put, HttpMethod.PUT, HTTP_200_OK), - (patch, HttpMethod.PATCH, HTTP_200_OK), - ], -) -def test_semantic_route_handlers_disallow_http_method_assignment( - sub: Any, http_method: Any, expected_status_code: int -) -> None: - result = sub()(dummy_method) - assert http_method in result.http_methods - assert result.status_code == expected_status_code - - with pytest.raises(ImproperlyConfiguredException): - sub(http_method=HttpMethod.GET if http_method != HttpMethod.GET else HttpMethod.POST) diff --git a/tests/unit/test_handlers/test_http_handlers/test_signature_namespace.py b/tests/unit/test_handlers/test_http_handlers/test_signature_namespace.py index eff63bf2b0..112af4ed24 100644 --- a/tests/unit/test_handlers/test_http_handlers/test_signature_namespace.py +++ b/tests/unit/test_handlers/test_http_handlers/test_signature_namespace.py @@ -6,17 +6,25 @@ from litestar import Controller, Router, delete, get, patch, post, put from litestar.testing import create_test_client +from litestar.types import HTTPHandlerDecorator @pytest.mark.parametrize( - ("method", "decorator"), [("GET", get), ("PUT", put), ("POST", post), ("PATCH", patch), ("DELETE", delete)] + ("method", "decorator"), + [ + ("GET", get), + ("PUT", put), + ("POST", post), + ("PATCH", patch), + ("DELETE", delete), + ], ) -def test_websocket_signature_namespace(method: str, decorator: type[get | put | post | patch | delete]) -> None: +def test_websocket_signature_namespace(method: str, decorator: HTTPHandlerDecorator) -> None: class MyController(Controller): path = "/" signature_namespace = {"c": float} - @decorator(path="/", signature_namespace={"d": List[str], "dict": Dict}, status_code=200) # type:ignore[misc] + @decorator(path="/", signature_namespace={"d": List[str], "dict": Dict}, status_code=200) async def simple_handler( self, a: a, # type:ignore[name-defined] # noqa: F821 diff --git a/tests/unit/test_handlers/test_http_handlers/test_validations.py b/tests/unit/test_handlers/test_http_handlers/test_validations.py index f0f492ee72..0ae5c233ed 100644 --- a/tests/unit/test_handlers/test_http_handlers/test_validations.py +++ b/tests/unit/test_handlers/test_http_handlers/test_validations.py @@ -21,19 +21,19 @@ def test_route_handler_validation_http_method() -> None: # doesn't raise for http methods for value in (*list(HttpMethod), *[x.upper() for x in list(HttpMethod)]): - assert route(http_method=value) # type: ignore[arg-type, truthy-bool] + assert route(http_method=value) # type: ignore[arg-type, truthy-function] # raises for invalid values with pytest.raises(ValidationException): - HTTPRouteHandler(http_method="deleze") # type: ignore[arg-type] + HTTPRouteHandler(http_method="deleze", fn=lambda: None) # type: ignore[arg-type] # also when passing an empty list with pytest.raises(ImproperlyConfiguredException): - route(http_method=[], status_code=HTTP_200_OK) + HTTPRouteHandler(http_method=[], status_code=HTTP_200_OK, fn=lambda: None) # also when passing malformed tokens with pytest.raises(ValidationException): - route(http_method=[HttpMethod.GET, "poft"], status_code=HTTP_200_OK) # type: ignore[list-item] + HTTPRouteHandler(http_method=[HttpMethod.GET, "poft"], status_code=HTTP_200_OK, fn=lambda: None) # type: ignore[list-item] async def test_function_validation() -> None: diff --git a/tests/unit/test_handlers/test_websocket_handlers/test_custom_handler_class.py b/tests/unit/test_handlers/test_websocket_handlers/test_custom_handler_class.py new file mode 100644 index 0000000000..e4b3afcb89 --- /dev/null +++ b/tests/unit/test_handlers/test_websocket_handlers/test_custom_handler_class.py @@ -0,0 +1,12 @@ +from litestar.handlers import WebsocketRouteHandler, websocket + + +def test_custom_handler_class() -> None: + class MyHandlerClass(WebsocketRouteHandler): + pass + + @websocket("/", handler_class=MyHandlerClass) + async def handler() -> None: + pass + + assert isinstance(handler, MyHandlerClass) diff --git a/tests/unit/test_handlers/test_websocket_handlers/test_listeners.py b/tests/unit/test_handlers/test_websocket_handlers/test_listeners.py index 46d27c1847..52b8e1dce0 100644 --- a/tests/unit/test_handlers/test_websocket_handlers/test_listeners.py +++ b/tests/unit/test_handlers/test_websocket_handlers/test_listeners.py @@ -11,6 +11,7 @@ from litestar.di import Provide from litestar.dto import DataclassDTO, dto_field from litestar.exceptions import ImproperlyConfiguredException +from litestar.handlers import WebsocketListenerRouteHandler from litestar.handlers.websocket_handlers import WebsocketListener, websocket_listener from litestar.routes import WebSocketRoute from litestar.testing import create_test_client @@ -28,21 +29,21 @@ def on_receive(self, data: str) -> str: # pyright: ignore @pytest.fixture -def sync_listener_callable(mock: MagicMock) -> websocket_listener: +def sync_listener_callable(mock: MagicMock) -> WebsocketListenerRouteHandler: def listener(data: str) -> str: mock(data) return data - return websocket_listener("/")(listener) + return WebsocketListenerRouteHandler("/", fn=listener) @pytest.fixture -def async_listener_callable(mock: MagicMock) -> websocket_listener: +def async_listener_callable(mock: MagicMock) -> WebsocketListenerRouteHandler: async def listener(data: str) -> str: mock(data) return data - return websocket_listener("/")(listener) + return WebsocketListenerRouteHandler("/", fn=listener) @pytest.mark.parametrize( @@ -53,7 +54,9 @@ async def listener(data: str) -> str: lf("listener_class"), ], ) -def test_basic_listener(mock: MagicMock, listener: Union[websocket_listener, Type[WebsocketListener]]) -> None: +def test_basic_listener( + mock: MagicMock, listener: Union[WebsocketListenerRouteHandler, Type[WebsocketListener]] +) -> None: client = create_test_client([listener]) with client.websocket_connect("/") as ws: ws.send_text("foo") diff --git a/tests/unit/test_kwargs/test_multipart_data.py b/tests/unit/test_kwargs/test_multipart_data.py index 50552c959d..1f0e15f3ff 100644 --- a/tests/unit/test_kwargs/test_multipart_data.py +++ b/tests/unit/test_kwargs/test_multipart_data.py @@ -394,10 +394,15 @@ async def hello_world(data: UploadFile = Body(media_type=RequestEncodingType.MUL assert response.status_code == HTTP_201_CREATED +@pytest.mark.parametrize("optional", [True, False]) @pytest.mark.parametrize("file_count", (1, 2)) -def test_upload_multiple_files(file_count: int) -> None: - @post("/") - async def handler(data: List[UploadFile] = Body(media_type=RequestEncodingType.MULTI_PART)) -> None: +def test_upload_multiple_files(file_count: int, optional: bool) -> None: + annotation = List[UploadFile] + if optional: + annotation = Optional[annotation] # type: ignore[misc, assignment] + + @post("/", signature_namespace={"annotation": annotation}) + async def handler(data: annotation = Body(media_type=RequestEncodingType.MULTI_PART)) -> None: # pyright: ignore[reportGeneralTypeIssues] assert len(data) == file_count for file in data: @@ -415,13 +420,20 @@ class Files: file_list: List[UploadFile] +# https://github.com/litestar-org/litestar/issues/3407 +@dataclass +class OptionalFiles: + file_list: Optional[List[UploadFile]] + + +@pytest.mark.parametrize("file_model", (Files, OptionalFiles)) @pytest.mark.parametrize("file_count", (1, 2)) -def test_upload_multiple_files_in_model(file_count: int) -> None: - @post("/") - async def handler(data: Files = Body(media_type=RequestEncodingType.MULTI_PART)) -> None: - assert len(data.file_list) == file_count +def test_upload_multiple_files_in_model(file_count: int, file_model: type[Files | OptionalFiles]) -> None: + @post("/", signature_namespace={"file_model": file_model}) + async def handler(data: file_model = Body(media_type=RequestEncodingType.MULTI_PART)) -> None: # type: ignore[valid-type] + assert len(data.file_list) == file_count # type: ignore[attr-defined] - for file in data.file_list: + for file in data.file_list: # type: ignore[attr-defined] assert await file.read() == b"1" with create_test_client([handler]) as client: diff --git a/tests/unit/test_kwargs/test_validations.py b/tests/unit/test_kwargs/test_validations.py index f0d524b0ec..0853be05c6 100644 --- a/tests/unit/test_kwargs/test_validations.py +++ b/tests/unit/test_kwargs/test_validations.py @@ -48,17 +48,17 @@ 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"]) + handler_with_path_param = decorator("/{" + reserved_kwarg + ":int}")(locals()["test_fn"]) # type: ignore[operator] 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"]) + handler_with_dependency = decorator("/", dependencies={reserved_kwarg: Provide(my_dependency)})(locals()["test_fn"]) # type: ignore[operator] 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"]) + handler_with_aliased_param = decorator("/")(locals()["test_fn"]) # type: ignore[operator] with pytest.raises(ImproperlyConfiguredException): Litestar(route_handlers=[handler_with_aliased_param]) diff --git a/tests/unit/test_logging/test_logging_config.py b/tests/unit/test_logging/test_logging_config.py index e318ed730f..80731ffe38 100644 --- a/tests/unit/test_logging/test_logging_config.py +++ b/tests/unit/test_logging/test_logging_config.py @@ -219,3 +219,16 @@ def test_customizing_handler(handlers: Any, expected_handler_class: Any, monkeyp else: formatter = root_logger_handler.formatter assert formatter._fmt == log_format + + +@pytest.mark.parametrize( + "traceback_line_limit, expected_warning_deprecation_called", + [ + [-1, False], + [20, True], + ], +) +def test_traceback_line_limit_deprecation(traceback_line_limit: int, expected_warning_deprecation_called: bool) -> None: + with patch("litestar.logging.config.warn_deprecation") as mock_warning_deprecation: + LoggingConfig(traceback_line_limit=traceback_line_limit) + assert mock_warning_deprecation.called is expected_warning_deprecation_called diff --git a/tests/unit/test_logging/test_structlog_config.py b/tests/unit/test_logging/test_structlog_config.py index 4f8e695496..30e80b7e5c 100644 --- a/tests/unit/test_logging/test_structlog_config.py +++ b/tests/unit/test_logging/test_structlog_config.py @@ -1,6 +1,7 @@ import datetime import sys from typing import Callable +from unittest.mock import patch import pytest import structlog @@ -155,3 +156,19 @@ def test_structlog_config_specify_processors(capsys: CaptureFixture) -> None: {"key": "value1", "event": "message1"}, {"key": "value2", "event": "message2"}, ] + + +@pytest.mark.parametrize( + "isatty, pretty_print_tty, expected_as_json", + [ + (True, True, False), + (True, False, True), + (False, True, True), + (False, False, True), + ], +) +def test_structlog_config_as_json(isatty: bool, pretty_print_tty: bool, expected_as_json: bool) -> None: + with patch("litestar.logging.config.sys.stderr.isatty") as isatty_mock: + isatty_mock.return_value = isatty + logging_config = StructLoggingConfig(pretty_print_tty=pretty_print_tty) + assert logging_config.as_json() is expected_as_json diff --git a/tests/unit/test_middleware/test_exception_handler_middleware.py b/tests/unit/test_middleware/test_exception_handler_middleware.py index 3530b54d9c..ebb65b28ad 100644 --- a/tests/unit/test_middleware/test_exception_handler_middleware.py +++ b/tests/unit/test_middleware/test_exception_handler_middleware.py @@ -3,13 +3,12 @@ from unittest.mock import MagicMock import pytest -from _pytest.capture import CaptureFixture from pytest_mock import MockerFixture from starlette.exceptions import HTTPException as StarletteHTTPException from structlog.testing import capture_logs from litestar import Litestar, MediaType, Request, Response, get -from litestar.exceptions import HTTPException, InternalServerException, ValidationException +from litestar.exceptions import HTTPException, InternalServerException, LitestarException, ValidationException from litestar.exceptions.responses._debug_response import get_symbol_name from litestar.logging.config import LoggingConfig, StructLoggingConfig from litestar.middleware._internal.exceptions.middleware import ( @@ -20,7 +19,7 @@ from litestar.status_codes import HTTP_400_BAD_REQUEST, HTTP_500_INTERNAL_SERVER_ERROR from litestar.testing import TestClient, create_test_client from litestar.types import ExceptionHandlersMap -from litestar.types.asgi_types import HTTPScope +from litestar.types.asgi_types import HTTPReceiveMessage, HTTPScope, Message, Receive, Scope, Send from litestar.utils.scope.state import ScopeState from tests.helpers import cleanup_logging_impl @@ -205,12 +204,10 @@ def handler() -> None: if should_log: assert len(caplog.records) == 1 assert caplog.records[0].levelname == "ERROR" - assert caplog.records[0].message.startswith( - "exception raised on http connection to route /test\n\nTraceback (most recent call last):\n" - ) + assert caplog.records[0].message.startswith("Uncaught exception (connection_type=http, path=/test):") else: assert not caplog.records - assert "exception raised on http connection request to route /test" not in response.text + assert "Uncaught exception" not in response.text @pytest.mark.parametrize( @@ -228,7 +225,6 @@ def handler() -> None: ) def test_exception_handler_struct_logging( get_logger: "GetLogger", - capsys: CaptureFixture, is_debug: bool, logging_config: Optional[LoggingConfig], should_log: bool, @@ -251,50 +247,12 @@ def handler() -> None: assert len(cap_logs) == 1 assert cap_logs[0].get("connection_type") == "http" assert cap_logs[0].get("path") == "/test" - assert cap_logs[0].get("traceback") - assert cap_logs[0].get("event") == "Uncaught Exception" + assert cap_logs[0].get("event") == "Uncaught exception" assert cap_logs[0].get("log_level") == "error" else: assert not cap_logs -def test_traceback_truncate_default_logging( - get_logger: "GetLogger", - caplog: "LogCaptureFixture", -) -> None: - @get("/test") - def handler() -> None: - raise ValueError("Test debug exception") - - app = Litestar([handler], logging_config=LoggingConfig(log_exceptions="always", traceback_line_limit=1)) - - with caplog.at_level("ERROR", "litestar"), TestClient(app=app) as client: - client.app.logger = get_logger("litestar") - response = client.get("/test") - assert response.status_code == HTTP_500_INTERNAL_SERVER_ERROR - assert "Internal Server Error" in response.text - - assert len(caplog.records) == 1 - assert caplog.records[0].levelname == "ERROR" - assert caplog.records[0].message == ( - "exception raised on http connection to route /test\n\nTraceback (most recent call last):\nValueError: Test debug exception\n" - ) - - -def test_traceback_truncate_struct_logging() -> None: - @get("/test") - def handler() -> None: - raise ValueError("Test debug exception") - - app = Litestar([handler], logging_config=StructLoggingConfig(log_exceptions="always", traceback_line_limit=1)) - - with TestClient(app=app) as client, capture_logs() as cap_logs: - response = client.get("/test") - assert response.status_code == HTTP_500_INTERNAL_SERVER_ERROR - assert len(cap_logs) == 1 - assert cap_logs[0].get("traceback") == "ValueError: Test debug exception\n" - - def handler(_: Any, __: Any) -> Any: return None @@ -358,9 +316,7 @@ def handler() -> None: assert "Test debug exception" in response.text assert len(caplog.records) == 1 assert caplog.records[0].levelname == "ERROR" - assert caplog.records[0].message.startswith( - "exception raised on http connection to route /test\n\nTraceback (most recent call last):\n" - ) + assert caplog.records[0].message.startswith("Uncaught exception (connection_type=http, path=/test):") def test_get_symbol_name_where_type_doesnt_support_bool() -> None: @@ -400,3 +356,29 @@ def handler() -> None: with create_test_client([handler], type_encoders={Foo: lambda f: f.value}) as client: res = client.get("/") assert res.json()["extra"] == {"foo": "bar"} + + +async def test_exception_handler_middleware_response_already_started(scope: HTTPScope) -> None: + assert not ScopeState.from_scope(scope).response_started + + async def mock_receive() -> HTTPReceiveMessage: # type: ignore[empty-body] + pass + + mock = MagicMock() + + async def mock_send(message: Message) -> None: + mock(message) + + start_message: Message = {"type": "http.response.start", "status": 200, "headers": []} + + async def asgi_app(scope: Scope, receive: Receive, send: Send) -> None: + await send(start_message) + raise RuntimeError("Test exception") + + mw = ExceptionHandlerMiddleware(asgi_app, None) + + with pytest.raises(LitestarException): + await mw(scope, mock_receive, mock_send) + + mock.assert_called_once_with(start_message) + assert ScopeState.from_scope(scope).response_started diff --git a/tests/unit/test_middleware/test_rate_limit_middleware.py b/tests/unit/test_middleware/test_rate_limit_middleware.py index 0c7a8a2ddb..c16577b2da 100644 --- a/tests/unit/test_middleware/test_rate_limit_middleware.py +++ b/tests/unit/test_middleware/test_rate_limit_middleware.py @@ -212,7 +212,7 @@ def handler() -> None: path1 = tmpdir / "test.css" path1.write_text("styles content", "utf-8") - asgi_handler = ASGIRouteHandler("/asgi", is_mount=True)(ASGIResponse(body="something")) + asgi_handler = ASGIRouteHandler("/asgi", is_mount=True, fn=ASGIResponse(body="something")) rate_limit_config = RateLimitConfig(rate_limit=("minute", 1), exclude=[r"^/src.*$"]) with create_test_client([handler, asgi_handler], middleware=[rate_limit_config.middleware]) as client: diff --git a/tests/unit/test_openapi/test_integration.py b/tests/unit/test_openapi/test_integration.py index 64516345c6..d6257a0693 100644 --- a/tests/unit/test_openapi/test_integration.py +++ b/tests/unit/test_openapi/test_integration.py @@ -85,6 +85,23 @@ def test_openapi_yaml_not_allowed( assert response.status_code == HTTP_404_NOT_FOUND +@pytest.mark.parametrize( + "schema_paths", + [ + ("/schema/openapi.json", "/schema/openapi.yaml"), + ("/schema/openapi.yaml", "/schema/openapi.json"), + ], +) +def test_openapi_controller_internal_schema_conversion(schema_paths: list[str]) -> None: + openapi_config = OpenAPIConfig("Example API", "1.0.0", render_plugins=(YamlRenderPlugin(),)) + + with create_test_client([], openapi_config=openapi_config) as client: + for schema_path in schema_paths: + response = client.get(schema_path) + assert response.status_code == HTTP_200_OK + assert "Example API" in response.text + + def test_openapi_custom_path() -> None: openapi_config = OpenAPIConfig(title="my title", version="1.0.0", path="/custom_schema_path") with create_test_client([], openapi_config=openapi_config) as client: diff --git a/tests/unit/test_openapi/test_path_item.py b/tests/unit/test_openapi/test_path_item.py index 1f0dadc5b3..a2401b1c86 100644 --- a/tests/unit/test_openapi/test_path_item.py +++ b/tests/unit/test_openapi/test_path_item.py @@ -8,12 +8,11 @@ import pytest from typing_extensions import TypeAlias -from litestar import Controller, HttpMethod, Litestar, Request, Router, delete, get +from litestar import Controller, HttpMethod, Litestar, Request, Router, delete, get, route from litestar._openapi.datastructures import OpenAPIContext from litestar._openapi.path_item import PathItemFactory, merge_path_item_operations from litestar._openapi.utils import default_operation_id_creator from litestar.exceptions import ImproperlyConfiguredException -from litestar.handlers.http_handlers import HTTPRouteHandler from litestar.openapi.config import OpenAPIConfig from litestar.openapi.spec import Operation, PathItem from litestar.utils import find_index @@ -23,7 +22,7 @@ @pytest.fixture() -def route(person_controller: type[Controller]) -> HTTPRoute: +def http_route(person_controller: type[Controller]) -> HTTPRoute: app = Litestar(route_handlers=[person_controller], openapi_config=None) index = find_index(app.routes, lambda x: x.path_format == "/{service_id}/person/{person_id}") return cast("HTTPRoute", app.routes[index]) @@ -59,8 +58,8 @@ def factory(route: HTTPRoute) -> PathItemFactory: return factory -def test_create_path_item(route: HTTPRoute, create_factory: CreateFactoryFixture) -> None: - schema = create_factory(route).create_path_item() +def test_create_path_item(http_route: HTTPRoute, create_factory: CreateFactoryFixture) -> None: + schema = create_factory(http_route).create_path_item() assert schema.delete assert schema.delete.operation_id == "ServiceIdPersonPersonIdDeletePerson" assert schema.delete.summary == "DeletePerson" @@ -79,7 +78,7 @@ def test_unique_operation_ids_for_multiple_http_methods(create_factory: CreateFa class MultipleMethodsRouteController(Controller): path = "/" - @HTTPRouteHandler("/", http_method=["GET", "HEAD"]) + @route("/", http_method=["GET", "HEAD"]) async def root(self, *, request: Request[str, str, Any]) -> None: pass @@ -100,7 +99,7 @@ def test_unique_operation_ids_for_multiple_http_methods_with_handler_level_opera class MultipleMethodsRouteController(Controller): path = "/" - @HTTPRouteHandler("/", http_method=["GET", "HEAD"], operation_id=default_operation_id_creator) + @route("/", http_method=["GET", "HEAD"], operation_id=default_operation_id_creator) async def root(self, *, request: Request[str, str, Any]) -> None: pass @@ -128,8 +127,10 @@ def test_routes_with_different_paths_should_generate_unique_operation_ids( assert schema_v1.get.operation_id != schema_v2.get.operation_id -def test_create_path_item_use_handler_docstring_false(route: HTTPRoute, create_factory: CreateFactoryFixture) -> None: - factory = create_factory(route) +def test_create_path_item_use_handler_docstring_false( + http_route: HTTPRoute, create_factory: CreateFactoryFixture +) -> None: + factory = create_factory(http_route) assert not factory.context.openapi_config.use_handler_docstrings schema = factory.create_path_item() assert schema.get @@ -138,8 +139,10 @@ def test_create_path_item_use_handler_docstring_false(route: HTTPRoute, create_f assert schema.patch.description == "Description in decorator" -def test_create_path_item_use_handler_docstring_true(route: HTTPRoute, create_factory: CreateFactoryFixture) -> None: - factory = create_factory(route) +def test_create_path_item_use_handler_docstring_true( + http_route: HTTPRoute, create_factory: CreateFactoryFixture +) -> None: + factory = create_factory(http_route) factory.context.openapi_config.use_handler_docstrings = True schema = factory.create_path_item() assert schema.get diff --git a/tests/unit/test_plugins/test_flash.py b/tests/unit/test_plugins/test_flash.py index 2a283b63fb..480c4cb61c 100644 --- a/tests/unit/test_plugins/test_flash.py +++ b/tests/unit/test_plugins/test_flash.py @@ -5,12 +5,15 @@ import pytest -from litestar import Request, get +from litestar import Litestar, Request, get, post from litestar.contrib.jinja import JinjaTemplateEngine from litestar.contrib.mako import MakoTemplateEngine from litestar.contrib.minijinja import MiniJinjaTemplateEngine +from litestar.exceptions import ImproperlyConfiguredException +from litestar.middleware.rate_limit import RateLimitConfig +from litestar.middleware.session.server_side import ServerSideSessionConfig from litestar.plugins.flash import FlashConfig, FlashPlugin, flash -from litestar.response import Template +from litestar.response import Redirect, Template from litestar.template import TemplateConfig, TemplateEngineProtocol from litestar.testing import create_test_client @@ -56,25 +59,55 @@ def test_flash_plugin( category_enum: Enum, ) -> None: Path(tmp_path / "flash.html").write_text(template_str) - text_expected = "".join( - [f'message {category.value}' for category in category_enum] # type: ignore[attr-defined] - ) - @get("/flash") - def flash_handler(request: Request) -> Template: - for category in category_enum: # type: ignore[attr-defined] - flash(request, f"message {category.value}", category=category.value) + @get("/") + async def index() -> Redirect: + return Redirect("/login") + + @get("/login") + async def login(request: Request) -> Template: + flash(request, "Flash Test!", category="info") return Template("flash.html") + @post("/check") + async def check(request: Request) -> Redirect: + flash(request, "User not Found!", category="warning") + return Redirect("/login") + template_config: TemplateConfig = TemplateConfig( directory=Path(tmp_path), engine=engine, ) + session_config = ServerSideSessionConfig() + flash_config = FlashConfig(template_config=template_config) with create_test_client( - [flash_handler], + plugins=[FlashPlugin(config=flash_config)], + route_handlers=[index, login, check], template_config=template_config, - plugins=[FlashPlugin(config=FlashConfig(template_config=template_config))], + middleware=[session_config.middleware], ) as client: - r = client.get("/flash") + r = client.get("/") + assert r.status_code == 200 + assert "Flash Test!" in r.text + r = client.get("/login") + assert r.status_code == 200 + assert "Flash Test!" in r.text + r = client.post("/check") assert r.status_code == 200 - assert r.text == text_expected + assert "User not Found!" in r.text + assert "Flash Test!" in r.text + + +def test_flash_config_doesnt_have_session() -> None: + template_config = TemplateConfig(directory=Path("tests/templates"), engine=JinjaTemplateEngine) + flash_config = FlashConfig(template_config=template_config) + with pytest.raises(ImproperlyConfiguredException): + Litestar(plugins=[FlashPlugin(config=flash_config)]) + + +def test_flash_config_has_wrong_middleware_type() -> None: + template_config = TemplateConfig(directory=Path("tests/templates"), engine=JinjaTemplateEngine) + flash_config = FlashConfig(template_config=template_config) + rate_limit_config = RateLimitConfig(rate_limit=("minute", 1), exclude=["/schema"]) + with pytest.raises(ImproperlyConfiguredException): + Litestar(plugins=[FlashPlugin(config=flash_config)], middleware=[rate_limit_config.middleware]) diff --git a/tests/unit/test_plugins/test_sqlalchemy.py b/tests/unit/test_plugins/test_sqlalchemy.py index 8ac4e31070..69512e1a9b 100644 --- a/tests/unit/test_plugins/test_sqlalchemy.py +++ b/tests/unit/test_plugins/test_sqlalchemy.py @@ -1,24 +1,27 @@ -from advanced_alchemy import base as sa_base -from advanced_alchemy import filters as sa_filters -from advanced_alchemy import types as sa_types 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 +from advanced_alchemy.extensions.litestar import filters as sa_filters +from advanced_alchemy.extensions.litestar import mixins as sa_mixins +from advanced_alchemy.extensions.litestar import repository as sa_repository +from advanced_alchemy.extensions.litestar import service as sa_service +from advanced_alchemy.extensions.litestar import types as sa_types +from advanced_alchemy.extensions.litestar import utils as sa_utils +from litestar.pagination import OffsetPagination from litestar.plugins import sqlalchemy def test_re_exports() -> None: + assert sqlalchemy.base is sa_base assert sqlalchemy.filters is sa_filters assert sqlalchemy.types is sa_types - - 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 + assert sqlalchemy.mixins is sa_mixins + assert sqlalchemy.utils is sa_utils + assert sqlalchemy.repository is sa_repository + assert sqlalchemy.service is sa_service + assert sqlalchemy.exceptions is sa_exceptions + assert OffsetPagination is sa_service.OffsetPagination assert sqlalchemy.AlembicAsyncConfig is sa_litestar.AlembicAsyncConfig assert sqlalchemy.AlembicCommands is sa_litestar.AlembicCommands @@ -33,3 +36,14 @@ def test_re_exports() -> None: assert sqlalchemy.SQLAlchemySerializationPlugin is sa_litestar.SQLAlchemySerializationPlugin assert sqlalchemy.SQLAlchemySyncConfig is sa_litestar.SQLAlchemySyncConfig 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 diff --git a/tests/unit/test_repository/models_bigint.py b/tests/unit/test_repository/models_bigint.py index 11865bbb3b..567f388034 100644 --- a/tests/unit/test_repository/models_bigint.py +++ b/tests/unit/test_repository/models_bigint.py @@ -5,8 +5,8 @@ from datetime import date, datetime from typing import List -from advanced_alchemy import SQLAlchemyAsyncRepository, SQLAlchemySyncRepository from advanced_alchemy.base import BigIntAuditBase, BigIntBase +from advanced_alchemy.repository import SQLAlchemyAsyncRepository, SQLAlchemySyncRepository from sqlalchemy import Column, FetchedValue, ForeignKey, String, Table, func from sqlalchemy.orm import Mapped, mapped_column, relationship diff --git a/tests/unit/test_repository/models_uuid.py b/tests/unit/test_repository/models_uuid.py index 8320fbb768..e58e07d3d7 100644 --- a/tests/unit/test_repository/models_uuid.py +++ b/tests/unit/test_repository/models_uuid.py @@ -6,13 +6,13 @@ from typing import List from uuid import UUID -from advanced_alchemy import SQLAlchemyAsyncRepository, SQLAlchemySyncRepository, base -from advanced_alchemy.base import UUIDAuditBase, UUIDBase +from advanced_alchemy import base +from advanced_alchemy.repository import SQLAlchemyAsyncRepository, SQLAlchemySyncRepository from sqlalchemy import Column, FetchedValue, ForeignKey, String, Table, func from sqlalchemy.orm import Mapped, mapped_column, relationship -class UUIDAuthor(UUIDAuditBase): +class UUIDAuthor(base.UUIDAuditBase): """The UUIDAuthor domain object.""" name: Mapped[str] = mapped_column(String(length=100)) # pyright: ignore @@ -24,7 +24,7 @@ class UUIDAuthor(UUIDAuditBase): ) -class UUIDBook(UUIDBase): +class UUIDBook(base.UUIDBase): """The Book domain object.""" title: Mapped[str] = mapped_column(String(length=250)) # pyright: ignore @@ -32,14 +32,14 @@ class UUIDBook(UUIDBase): author: Mapped[UUIDAuthor] = relationship(lazy="joined", innerjoin=True, back_populates="books") # pyright: ignore -class UUIDEventLog(UUIDAuditBase): +class UUIDEventLog(base.UUIDAuditBase): """The event log domain object.""" logged_at: Mapped[datetime] = mapped_column(default=datetime.now()) # pyright: ignore payload: Mapped[dict] = mapped_column(default={}) # pyright: ignore -class UUIDModelWithFetchedValue(UUIDBase): +class UUIDModelWithFetchedValue(base.UUIDBase): """The ModelWithFetchedValue UUIDBase.""" val: Mapped[int] # pyright: ignore @@ -58,20 +58,20 @@ class UUIDModelWithFetchedValue(UUIDBase): ) -class UUIDItem(UUIDBase): +class UUIDItem(base.UUIDBase): name: Mapped[str] = mapped_column(String(length=50)) # pyright: ignore description: Mapped[str] = mapped_column(String(length=100), nullable=True) # pyright: ignore tags: Mapped[List[UUIDTag]] = relationship(secondary=lambda: uuid_item_tag, back_populates="items") # noqa -class UUIDTag(UUIDAuditBase): +class UUIDTag(base.UUIDAuditBase): """The event log domain object.""" name: Mapped[str] = mapped_column(String(length=50)) # pyright: ignore items: Mapped[List[UUIDItem]] = relationship(secondary=lambda: uuid_item_tag, back_populates="tags") # noqa -class UUIDRule(UUIDAuditBase): +class UUIDRule(base.UUIDAuditBase): """The rule domain object.""" name: Mapped[str] = mapped_column(String(length=250)) # pyright: ignore diff --git a/tests/unit/test_signature/test_validation.py b/tests/unit/test_signature/test_validation.py index 647a9d49c3..ef618dd10b 100644 --- a/tests/unit/test_signature/test_validation.py +++ b/tests/unit/test_signature/test_validation.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import List, Optional +from typing import Generic, List, Optional, TypeVar import pytest from attr import define @@ -120,7 +120,7 @@ def handler(data: Parent) -> None: model = SignatureModel.create( dependency_name_set=set(), - fn=handler, + fn=handler, # type: ignore[arg-type] data_dto=None, parsed_signature=ParsedSignature.from_fn(handler.fn, {}), type_decoders=[], @@ -289,3 +289,17 @@ def fn(a: Annotated[int, Parameter(gt=5)], b: Annotated[int, Parameter(lt=5)]) - {"message": "Expected `int` >= 6", "key": "a", "source": ParamType.QUERY}, {"message": "Expected `int` <= 4", "key": "b", "source": ParamType.QUERY}, ] + + +def test_validate_subscribed_generics() -> None: + T = TypeVar("T") + + class Foo(Generic[T]): + pass + + @get("/") + async def something(foo: Foo[str] = Foo()) -> None: + return None + + with create_test_client([something]) as client: + assert client.get("/").status_code == 200 diff --git a/tests/unit/test_static_files/test_file_serving_resolution.py b/tests/unit/test_static_files/test_file_serving_resolution.py index 731fa18dea..798b47f20d 100644 --- a/tests/unit/test_static_files/test_file_serving_resolution.py +++ b/tests/unit/test_static_files/test_file_serving_resolution.py @@ -9,7 +9,8 @@ import pytest from litestar import MediaType, get -from litestar.static_files import create_static_files_router +from litestar.file_system import FileSystemAdapter +from litestar.static_files import _get_fs_info, create_static_files_router from litestar.status_codes import HTTP_200_OK from litestar.testing import create_test_client @@ -251,3 +252,29 @@ def test_resolve_symlinks(tmp_path: Path, resolve: bool) -> None: assert client.get("/test.txt").status_code == 404 else: assert client.get("/test.txt").status_code == 200 + + +async def test_staticfiles_get_fs_info_no_access_to_non_static_directory( + tmp_path: Path, + file_system: FileSystemProtocol, +) -> None: + assets = tmp_path / "assets" + assets.mkdir() + index = tmp_path / "index.html" + index.write_text("content", "utf-8") + path, info = await _get_fs_info([assets], "../index.html", adapter=FileSystemAdapter(file_system)) + assert path is None + assert info is None + + +async def test_staticfiles_get_fs_info_no_access_to_non_static_file_with_prefix( + tmp_path: Path, + file_system: FileSystemProtocol, +) -> None: + static = tmp_path / "static" + static.mkdir() + private_file = tmp_path / "staticsecrets.env" + private_file.write_text("content", "utf-8") + path, info = await _get_fs_info([static], "../staticsecrets.env", adapter=FileSystemAdapter(file_system)) + assert path is None + assert info is None diff --git a/tests/unit/test_static_files/test_static_files_validation.py b/tests/unit/test_static_files/test_static_files_validation.py index b808aa43c9..57ef6a4b59 100644 --- a/tests/unit/test_static_files/test_static_files_validation.py +++ b/tests/unit/test_static_files/test_static_files_validation.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, List +from pathlib import Path import pytest @@ -8,14 +8,10 @@ from litestar.status_codes import HTTP_200_OK, HTTP_204_NO_CONTENT, HTTP_405_METHOD_NOT_ALLOWED from litestar.testing import create_test_client -if TYPE_CHECKING: - from pathlib import Path - -@pytest.mark.parametrize("directories", [[], [""]]) -def test_validation_of_directories(directories: List[str]) -> None: +def test_validation_of_directories() -> None: with pytest.raises(ImproperlyConfiguredException): - create_static_files_router(path="/static", directories=directories) + create_static_files_router(path="/static", directories=[]) def test_validation_of_path(tmpdir: "Path") -> None: diff --git a/tests/unit/test_stores.py b/tests/unit/test_stores.py index e09ffa6391..f28b71017c 100644 --- a/tests/unit/test_stores.py +++ b/tests/unit/test_stores.py @@ -250,6 +250,19 @@ async def test_file_init_directory(file_store: FileStore) -> None: await file_store.set("foo", b"bar") +async def test_file_init_subdirectory(file_store_create_directories: FileStore) -> None: + file_store = file_store_create_directories + async with file_store: + await file_store.set("foo", b"bar") + + +async def test_file_init_subdirectory_negative(file_store_create_directories_flag_false: FileStore) -> None: + file_store = file_store_create_directories_flag_false + async with file_store: + with pytest.raises(FileNotFoundError): + await file_store.set("foo", b"bar") + + async def test_file_path(file_store: FileStore) -> None: await file_store.set("foo", b"bar") diff --git a/tools/prepare_release.py b/tools/prepare_release.py index e081825f38..7192eadee7 100644 --- a/tools/prepare_release.py +++ b/tools/prepare_release.py @@ -78,8 +78,8 @@ def _pr_number_from_commit(comp: Comp) -> int: message_head = comp.commit.message.split("\n\n")[0] match = re.search(r"\(#(\d+)\)$", message_head) if not match: - raise ValueError(f"Could not find PR number for commit {message_head!r}") - return int(match[1]) + print(f"Could not find PR number in {message_head}") # noqa: T201 + return int(match[1]) if match else None class _Thing: @@ -152,7 +152,7 @@ async def get_prs(self) -> dict[str, list[PRInfo]]: res = await self._api_client.get(f"/compare/{self._base}...{self._release_branch}") res.raise_for_status() compares = msgspec.convert(res.json()["commits"], list[Comp]) - pr_numbers = [_pr_number_from_commit(c) for c in compares] + pr_numbers = list(filter(None, (_pr_number_from_commit(c) for c in compares))) pulls = await asyncio.gather(*map(self._get_pr_info_for_pr, pr_numbers)) prs = defaultdict(list) @@ -203,12 +203,12 @@ async def get_release_info(self) -> ReleaseInfo: version=self._new_release_version, ) - async def create_draft_release(self, body: str) -> str: + async def create_draft_release(self, body: str, release_branch: str) -> str: res = await self._api_client.post( "/releases", json={ "tag_name": self._new_release_tag, - "target_commitish": "main", + "target_commitish": release_branch, "name": self._new_release_tag, "draft": True, "body": body, @@ -432,7 +432,7 @@ def cli( if create_draft_release: click.secho("Creating draft release", fg="blue") - release_url = loop.run_until_complete(thing.create_draft_release(body=gh_release_notes)) + release_url = loop.run_until_complete(thing.create_draft_release(body=gh_release_notes, release_branch=branch)) click.echo(f"Draft release available at: {release_url}") else: click.echo(gh_release_notes)