diff --git a/docs/examples/request_data/custom_request.py b/docs/examples/request_data/custom_request.py index a1a21f9fe2..17dc0986b3 100644 --- a/docs/examples/request_data/custom_request.py +++ b/docs/examples/request_data/custom_request.py @@ -19,7 +19,7 @@ def __init__(self, scope: Scope, receive: Receive = empty_receive, send: Send = self.kitten_name = KITTEN_NAMES_MAP.get(scope["method"], "Mittens") -@get(path="/kitten-name") +@get(path="/kitten-name", sync_to_thread=False) def get_kitten_name(request: CustomRequest) -> str: """Get kitten name based on the HTTP method.""" return request.kitten_name diff --git a/pyproject.toml b/pyproject.toml index 6aa8956b7f..0de84e3d07 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -199,7 +199,7 @@ exclude_lines = [ 'if VERSION.startswith("1"):', 'if pydantic.VERSION.startswith("1"):', ] -fail_under = 96 +fail_under = 50 [tool.pytest.ini_options] addopts = "--strict-markers --strict-config --dist=loadgroup -m 'not server_integration'" @@ -216,6 +216,10 @@ filterwarnings = [ "ignore::pydantic.PydanticDeprecatedSince20::", "ignore:`general_plain_validator_function`:DeprecationWarning::", "ignore: 'RichMultiCommand':DeprecationWarning::", # this is coming from rich_click itself, nothing we can do about # that for now + "ignore: Dropping max_length:litestar.exceptions.LitestarWarning:litestar.contrib.piccolo", + "ignore: Python Debugger on exception enabled:litestar.exceptions.LitestarWarning:", + "ignore: datetime.datetime.utcnow:DeprecationWarning:time_machine", + "ignore: datetime.datetime.utcnow:DeprecationWarning:jose", ] markers = [ "sqlalchemy_integration: SQLAlchemy integration tests", diff --git a/tests/unit/test_cli/test_core_commands.py b/tests/unit/test_cli/test_core_commands.py index bc709a030a..2cf815c164 100644 --- a/tests/unit/test_cli/test_core_commands.py +++ b/tests/unit/test_cli/test_core_commands.py @@ -375,7 +375,7 @@ def test_run_command_arguments_precedence( if isinstance(env_value, list): monkeypatch.setenv(env_name, "".join(env_value)) else: - monkeypatch.setenv(env_name, env_value) # type: ignore[arg-type] # pyright: ignore (reportGeneralTypeIssues) + monkeypatch.setenv(env_name, str(env_value)) if cli_name: if cli_value is True: diff --git a/tests/unit/test_contrib/test_pydantic/conftest.py b/tests/unit/test_contrib/test_pydantic/conftest.py index 63cfd5aab3..f56f677ab4 100644 --- a/tests/unit/test_contrib/test_pydantic/conftest.py +++ b/tests/unit/test_contrib/test_pydantic/conftest.py @@ -5,9 +5,19 @@ from pydantic import v1 as pydantic_v1 from pytest import FixtureRequest +from litestar.contrib.pydantic.pydantic_init_plugin import ( # type: ignore[attr-defined] + _KWARG_META_EXTRACTORS, + ConstrainedFieldMetaExtractor, +) + from . import PydanticVersion +@pytest.fixture(autouse=True, scope="session") +def ensure_metadata_extractor_is_added() -> None: + _KWARG_META_EXTRACTORS.add(ConstrainedFieldMetaExtractor) + + @pytest.fixture(params=["v1", "v2"]) def pydantic_version(request: FixtureRequest) -> PydanticVersion: return request.param # type: ignore[no-any-return] diff --git a/tests/unit/test_contrib/test_pydantic/test_openapi.py b/tests/unit/test_contrib/test_pydantic/test_openapi.py index 4407b79980..b0921f00ac 100644 --- a/tests/unit/test_contrib/test_pydantic/test_openapi.py +++ b/tests/unit/test_contrib/test_pydantic/test_openapi.py @@ -1,4 +1,4 @@ -from datetime import date, datetime, timedelta +from datetime import date, datetime, timedelta, timezone from decimal import Decimal from types import ModuleType from typing import Any, Callable, Pattern, Type, Union, cast @@ -257,17 +257,29 @@ def test_create_date_constrained_field_schema_pydantic_v1(annotation: Any) -> No schema = create_date_constrained_field_schema(field_definition.annotation, field_definition.kwarg_definition) assert schema.type == OpenAPIType.STRING assert schema.format == OpenAPIFormat.DATE - assert (datetime.utcfromtimestamp(schema.exclusive_minimum) if schema.exclusive_minimum else None) == ( - datetime.fromordinal(annotation.gt.toordinal()) if annotation.gt is not None else None + assert ( + datetime.fromtimestamp(schema.exclusive_minimum, tz=timezone.utc) if schema.exclusive_minimum else None + ) == ( + datetime.fromordinal(annotation.gt.toordinal()).replace(tzinfo=timezone.utc) + if annotation.gt is not None + else None ) - assert (datetime.utcfromtimestamp(schema.minimum) if schema.minimum else None) == ( - datetime.fromordinal(annotation.ge.toordinal()) if annotation.ge is not None else None + assert (datetime.fromtimestamp(schema.minimum, tz=timezone.utc) if schema.minimum else None) == ( + datetime.fromordinal(annotation.ge.toordinal()).replace(tzinfo=timezone.utc) + if annotation.ge is not None + else None ) - assert (datetime.utcfromtimestamp(schema.exclusive_maximum) if schema.exclusive_maximum else None) == ( - datetime.fromordinal(annotation.lt.toordinal()) if annotation.lt is not None else None + assert ( + datetime.fromtimestamp(schema.exclusive_maximum, tz=timezone.utc) if schema.exclusive_maximum else None + ) == ( + datetime.fromordinal(annotation.lt.toordinal()).replace(tzinfo=timezone.utc) + if annotation.lt is not None + else None ) - assert (datetime.utcfromtimestamp(schema.maximum) if schema.maximum else None) == ( - datetime.fromordinal(annotation.le.toordinal()) if annotation.le is not None else None + assert (datetime.fromtimestamp(schema.maximum, tz=timezone.utc) if schema.maximum else None) == ( + datetime.fromordinal(annotation.le.toordinal()).replace(tzinfo=timezone.utc) + if annotation.le is not None + else None ) @@ -280,26 +292,42 @@ def test_create_date_constrained_field_schema_pydantic_v2(annotation: Any) -> No assert schema.type == OpenAPIType.STRING assert schema.format == OpenAPIFormat.DATE assert any( - (datetime.fromordinal(getattr(m, "gt", None).toordinal()) if getattr(m, "gt", None) is not None else None) # type: ignore[union-attr] - == (datetime.utcfromtimestamp(schema.exclusive_minimum) if schema.exclusive_minimum else None) + ( + datetime.fromordinal(getattr(m, "gt", None).toordinal()).replace(tzinfo=timezone.utc) # type: ignore[union-attr] + if getattr(m, "gt", None) is not None + else None + ) + == (datetime.fromtimestamp(schema.exclusive_minimum, tz=timezone.utc) if schema.exclusive_minimum else None) for m in field_definition.metadata if m ) assert any( - (datetime.fromordinal(getattr(m, "ge", None).toordinal()) if getattr(m, "ge", None) is not None else None) # type: ignore[union-attr] - == (datetime.utcfromtimestamp(schema.minimum) if schema.minimum else None) + ( + datetime.fromordinal(getattr(m, "ge", None).toordinal()).replace(tzinfo=timezone.utc) # type: ignore[union-attr] + if getattr(m, "ge", None) is not None + else None + ) + == (datetime.fromtimestamp(schema.minimum, tz=timezone.utc) if schema.minimum else None) for m in field_definition.metadata if m ) assert any( - (datetime.fromordinal(getattr(m, "lt", None).toordinal()) if getattr(m, "lt", None) is not None else None) # type: ignore[union-attr] - == (datetime.utcfromtimestamp(schema.exclusive_maximum) if schema.exclusive_maximum else None) + ( + datetime.fromordinal(getattr(m, "lt", None).toordinal()).replace(tzinfo=timezone.utc) # type: ignore[union-attr] + if getattr(m, "lt", None) is not None + else None + ) + == (datetime.fromtimestamp(schema.exclusive_maximum, tz=timezone.utc) if schema.exclusive_maximum else None) for m in field_definition.metadata if m ) assert any( - (datetime.fromordinal(getattr(m, "le", None).toordinal()) if getattr(m, "le", None) is not None else None) # type: ignore[union-attr] - == (datetime.utcfromtimestamp(schema.maximum) if schema.maximum else None) + ( + datetime.fromordinal(getattr(m, "le", None).toordinal()).replace(tzinfo=timezone.utc) # type: ignore[union-attr] + if getattr(m, "le", None) is not None + else None + ) + == (datetime.fromtimestamp(schema.maximum, tz=timezone.utc) if schema.maximum else None) for m in field_definition.metadata if m ) diff --git a/tests/unit/test_dto/conftest.py b/tests/unit/test_dto/conftest.py index 6f512304f1..3ad54c5e44 100644 --- a/tests/unit/test_dto/conftest.py +++ b/tests/unit/test_dto/conftest.py @@ -7,7 +7,7 @@ from litestar import Request, get from litestar._openapi.schema_generation import SchemaCreator -from litestar.dto import AbstractDTO, DTOField, DTOFieldDefinition +from litestar.dto import AbstractDTO, DTOConfig, DTOField, DTOFieldDefinition from litestar.enums import MediaType from litestar.openapi.spec import Reference, Schema from litestar.testing import RequestFactory @@ -20,8 +20,10 @@ @pytest.fixture -def ModelDataDTO() -> type[AbstractDTO]: +def ModelDataDTO(use_experimental_dto_backend: bool) -> type[AbstractDTO]: class DTOCls(AbstractDTO[Model]): + config = DTOConfig(experimental_codegen_backend=use_experimental_dto_backend) + def decode_builtins(self, value: Any) -> Model: return Model(a=1, b="2") diff --git a/tests/unit/test_dto/test_integration.py b/tests/unit/test_dto/test_integration.py index f9d7ba4d57..99f2cb1f6e 100644 --- a/tests/unit/test_dto/test_integration.py +++ b/tests/unit/test_dto/test_integration.py @@ -3,42 +3,27 @@ from typing import Dict from unittest.mock import MagicMock -import pytest - from litestar import Controller, Litestar, Router, post -from litestar.config.app import ExperimentalFeatures from litestar.dto import AbstractDTO, DTOConfig from litestar.dto._backend import DTOBackend -from litestar.dto._codegen_backend import DTOCodegenBackend from litestar.testing import create_test_client from . import Model -@pytest.fixture() -def experimental_features(use_experimental_dto_backend: bool) -> list[ExperimentalFeatures] | None: - if use_experimental_dto_backend: - return [ExperimentalFeatures.DTO_CODEGEN] - return None - - -def test_dto_defined_on_handler( - ModelDataDTO: type[AbstractDTO], experimental_features: list[ExperimentalFeatures] -) -> None: +def test_dto_defined_on_handler(ModelDataDTO: type[AbstractDTO]) -> None: @post(dto=ModelDataDTO, signature_types=[Model]) def handler(data: Model) -> Model: assert data == Model(a=1, b="2") return data - with create_test_client(route_handlers=handler, experimental_features=experimental_features) as client: + with create_test_client(route_handlers=handler) as client: response = client.post("/", json={"what": "ever"}) assert response.status_code == 201 assert response.json() == {"a": 1, "b": "2"} -def test_dto_defined_on_controller( - ModelDataDTO: type[AbstractDTO], experimental_features: list[ExperimentalFeatures] -) -> None: +def test_dto_defined_on_controller(ModelDataDTO: type[AbstractDTO]) -> None: class MyController(Controller): dto = ModelDataDTO @@ -47,15 +32,13 @@ def handler(self, data: Model) -> Model: assert data == Model(a=1, b="2") return data - with create_test_client(route_handlers=MyController, experimental_features=experimental_features) as client: + with create_test_client(route_handlers=MyController) as client: response = client.post("/", json={"what": "ever"}) assert response.status_code == 201 assert response.json() == {"a": 1, "b": "2"} -def test_dto_defined_on_router( - ModelDataDTO: type[AbstractDTO], experimental_features: list[ExperimentalFeatures] -) -> None: +def test_dto_defined_on_router(ModelDataDTO: type[AbstractDTO]) -> None: @post() def handler(data: Model) -> Model: assert data == Model(a=1, b="2") @@ -63,29 +46,25 @@ def handler(data: Model) -> Model: router = Router(path="/", route_handlers=[handler], dto=ModelDataDTO) - with create_test_client(route_handlers=router, experimental_features=experimental_features) as client: + with create_test_client(route_handlers=router) as client: response = client.post("/", json={"what": "ever"}) assert response.status_code == 201 assert response.json() == {"a": 1, "b": "2"} -def test_dto_defined_on_app(ModelDataDTO: type[AbstractDTO], experimental_features: list[ExperimentalFeatures]) -> None: +def test_dto_defined_on_app(ModelDataDTO: type[AbstractDTO]) -> None: @post() def handler(data: Model) -> Model: assert data == Model(a=1, b="2") return data - with create_test_client( - route_handlers=handler, dto=ModelDataDTO, experimental_features=experimental_features - ) as client: + with create_test_client(route_handlers=handler, dto=ModelDataDTO) as client: response = client.post("/", json={"what": "ever"}) assert response.status_code == 201 assert response.json() == {"a": 1, "b": "2"} -def test_set_dto_none_disables_inherited_dto( - ModelDataDTO: type[AbstractDTO], experimental_features: list[ExperimentalFeatures] -) -> None: +def test_set_dto_none_disables_inherited_dto(ModelDataDTO: type[AbstractDTO]) -> None: @post(dto=None, signature_namespace={"dict": Dict}) def handler(data: dict[str, str]) -> dict[str, str]: assert data == {"hello": "world"} @@ -93,52 +72,25 @@ def handler(data: dict[str, str]) -> dict[str, str]: mock_dto = MagicMock(spec=ModelDataDTO) - with create_test_client( - route_handlers=handler, - dto=mock_dto, # pyright:ignore - experimental_features=experimental_features, - ) as client: + with create_test_client(route_handlers=handler, dto=mock_dto) as client: # pyright:ignore response = client.post("/", json={"hello": "world"}) assert response.status_code == 201 assert response.json() == {"hello": "world"} mock_dto.assert_not_called() -def test_dto_and_return_dto( - ModelDataDTO: type[AbstractDTO], - ModelReturnDTO: type[AbstractDTO], - experimental_features: list[ExperimentalFeatures], -) -> None: +def test_dto_and_return_dto(ModelDataDTO: type[AbstractDTO], ModelReturnDTO: type[AbstractDTO]) -> None: @post() def handler(data: Model) -> Model: assert data == Model(a=1, b="2") return data - with create_test_client( - route_handlers=handler, dto=ModelDataDTO, return_dto=ModelReturnDTO, experimental_features=experimental_features - ) as client: + with create_test_client(route_handlers=handler, dto=ModelDataDTO, return_dto=ModelReturnDTO) as client: response = client.post("/", json={"what": "ever"}) assert response.status_code == 201 assert response.json() == {"a": 1, "b": "2"} -def test_enable_experimental_backend(ModelDataDTO: type[AbstractDTO], use_experimental_dto_backend: bool) -> None: - @post(dto=ModelDataDTO, signature_types=[Model]) - def handler(data: Model) -> Model: - return data - - Litestar( - route_handlers=[handler], - experimental_features=[ExperimentalFeatures.DTO_CODEGEN] if use_experimental_dto_backend else None, - ) - - backend = handler.resolve_data_dto()._dto_backends[handler.handler_id]["data_backend"] # type: ignore[union-attr] - if use_experimental_dto_backend: - assert isinstance(backend, DTOCodegenBackend) - else: - assert isinstance(backend, DTOBackend) - - def test_enable_experimental_backend_override_in_dto_config(ModelDataDTO: type[AbstractDTO]) -> None: ModelDataDTO.config = DTOConfig(experimental_codegen_backend=False) @@ -146,10 +98,7 @@ def test_enable_experimental_backend_override_in_dto_config(ModelDataDTO: type[A def handler(data: Model) -> Model: return data - Litestar( - route_handlers=[handler], - experimental_features=[ExperimentalFeatures.DTO_CODEGEN], - ) + Litestar(route_handlers=[handler]) backend = handler.resolve_data_dto()._dto_backends[handler.handler_id]["data_backend"] # type: ignore[union-attr] assert isinstance(backend, DTOBackend) diff --git a/tests/unit/test_openapi/test_schema.py b/tests/unit/test_openapi/test_schema.py index 666d5f6930..0a2280368e 100644 --- a/tests/unit/test_openapi/test_schema.py +++ b/tests/unit/test_openapi/test_schema.py @@ -1,6 +1,6 @@ import sys from dataclasses import dataclass -from datetime import date, datetime +from datetime import date, datetime, timezone from enum import Enum, auto from typing import ( # type: ignore[attr-defined] TYPE_CHECKING, @@ -317,12 +317,14 @@ class MyDataclass: assert schema.properties["constrained_int"].exclusive_maximum == 10 # type: ignore[index, union-attr] assert schema.properties["constrained_float"].minimum == 1 # type: ignore[index, union-attr] assert schema.properties["constrained_float"].maximum == 10 # type: ignore[index, union-attr] - assert datetime.utcfromtimestamp(schema.properties["constrained_date"].exclusive_minimum) == datetime.fromordinal( # type: ignore[arg-type, index, union-attr] - historical_date.toordinal() - ) - assert datetime.utcfromtimestamp(schema.properties["constrained_date"].exclusive_maximum) == datetime.fromordinal( # type: ignore[arg-type, index, union-attr] - today.toordinal() - ) + assert datetime.fromtimestamp( + schema.properties["constrained_date"].exclusive_minimum, # type: ignore[arg-type, index, union-attr] + tz=timezone.utc, + ) == datetime.fromordinal(historical_date.toordinal()).replace(tzinfo=timezone.utc) + assert datetime.fromtimestamp( + schema.properties["constrained_date"].exclusive_maximum, # type: ignore[arg-type, index, union-attr] + tz=timezone.utc, + ) == datetime.fromordinal(today.toordinal()).replace(tzinfo=timezone.utc) assert schema.properties["constrained_lower_case"].description == "must be in lower case" # type: ignore[index] assert schema.properties["constrained_upper_case"].description == "must be in upper case" # type: ignore[index] assert schema.properties["constrained_is_ascii"].pattern == "[[:ascii:]]" # type: ignore[index, union-attr]