From e87f68fb8cc0e2b877a0c0d64395dcaea7c76241 Mon Sep 17 00:00:00 2001 From: Cody Fincher Date: Sat, 16 Nov 2024 23:07:58 +0000 Subject: [PATCH 1/5] feat: move `litestar.contrib.attrs` to `litestar.plugins.attrs` --- Makefile | 2 +- docs/conf.py | 2 + docs/reference/plugins/attrs.rst | 5 ++ docs/reference/plugins/index.rst | 1 + litestar/contrib/attrs/__init__.py | 31 +++++++- litestar/contrib/attrs/attrs_schema_plugin.py | 70 +++++-------------- litestar/plugins/attrs.py | 65 +++++++++++++++++ tests/unit/test_contrib/test_attrs.py | 43 ++++++++++++ .../test_attrs/__init__.py | 0 .../test_attrs/test_inject_attrs_class.py | 0 .../test_attrs/test_schema_plugin.py | 2 +- .../test_attrs/test_schema_spec_generation.py | 0 .../test_attrs/test_signature.py | 0 tests/unit/test_plugins/test_base.py | 2 +- 14 files changed, 166 insertions(+), 57 deletions(-) create mode 100644 docs/reference/plugins/attrs.rst create mode 100644 litestar/plugins/attrs.py create mode 100644 tests/unit/test_contrib/test_attrs.py rename tests/unit/{test_contrib => test_plugins}/test_attrs/__init__.py (100%) rename tests/unit/{test_contrib => test_plugins}/test_attrs/test_inject_attrs_class.py (100%) rename tests/unit/{test_contrib => test_plugins}/test_attrs/test_schema_plugin.py (94%) rename tests/unit/{test_contrib => test_plugins}/test_attrs/test_schema_spec_generation.py (100%) rename tests/unit/{test_contrib => test_plugins}/test_attrs/test_signature.py (100%) diff --git a/Makefile b/Makefile index b43b1ae0c1..7b23034d75 100644 --- a/Makefile +++ b/Makefile @@ -51,7 +51,7 @@ clean: ## Cleanup temporary build artifacts @echo "=> Cleaning working directory" @rm -rf .pytest_cache .ruff_cache .hypothesis build/ -rf dist/ .eggs/ @find . -name '*.egg-info' -exec rm -rf {} + - @find . -name '*.egg' -exec rm -f {} + + @find . -type f -name '*.egg' -exec rm -f {} + @find . -name '*.pyc' -exec rm -f {} + @find . -name '*.pyo' -exec rm -f {} + @find . -name '*~' -exec rm -f {} + diff --git a/docs/conf.py b/docs/conf.py index 019d8dfa46..c47306c634 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -202,6 +202,8 @@ (PY_CLASS, "litestar.contrib.pydantic.PydanticDTO"), (PY_CLASS, "litestar.contrib.pydantic.PydanticPlugin"), (PY_CLASS, "typing.Self"), + (PY_CLASS, "attr.AttrsInstance"), + (PY_CLASS, "typing_extensions.TypeGuard"), ] nitpick_ignore_regex = [ diff --git a/docs/reference/plugins/attrs.rst b/docs/reference/plugins/attrs.rst new file mode 100644 index 0000000000..e3bcbb96cd --- /dev/null +++ b/docs/reference/plugins/attrs.rst @@ -0,0 +1,5 @@ +attrs +===== + +.. automodule:: litestar.plugins.attrs + :members: diff --git a/docs/reference/plugins/index.rst b/docs/reference/plugins/index.rst index 50bf99e769..69b0063948 100644 --- a/docs/reference/plugins/index.rst +++ b/docs/reference/plugins/index.rst @@ -9,6 +9,7 @@ plugins :maxdepth: 1 :hidden: + attrs flash_messages htmx problem_details diff --git a/litestar/contrib/attrs/__init__.py b/litestar/contrib/attrs/__init__.py index ddd2a3f9b8..f6490eee30 100644 --- a/litestar/contrib/attrs/__init__.py +++ b/litestar/contrib/attrs/__init__.py @@ -1,3 +1,30 @@ -from .attrs_schema_plugin import AttrsSchemaPlugin +# ruff: noqa: TCH004, F401 +from __future__ import annotations -__all__ = ("AttrsSchemaPlugin",) +from typing import TYPE_CHECKING, Any + +from litestar.utils import warn_deprecation + +__all__ = ("AttrsSchemaPlugin", "is_attrs_class") + + +def __getattr__(attr_name: str) -> object: + if attr_name in __all__: + from litestar.plugins.attrs import AttrsSchemaPlugin, is_attrs_class + + warn_deprecation( + deprecated_name=f"litestar.contrib.attrs.{attr_name}", + version="2.12", + kind="import", + removal_in="3.0", + info=f"importing {attr_name} from 'litestar.contrib.attrs' is deprecated, please " + f"import it from 'litestar.plugins.attrs' instead", + ) + value = globals()[attr_name] = locals()[attr_name] + return value + + raise AttributeError(f"module {__name__!r} has no attribute {attr_name!r}") # pragma: no cover + + +if TYPE_CHECKING: + from litestar.plugins.attrs import AttrsSchemaPlugin, is_attrs_class diff --git a/litestar/contrib/attrs/attrs_schema_plugin.py b/litestar/contrib/attrs/attrs_schema_plugin.py index 76f7491982..3f88cd787d 100644 --- a/litestar/contrib/attrs/attrs_schema_plugin.py +++ b/litestar/contrib/attrs/attrs_schema_plugin.py @@ -1,64 +1,30 @@ +# ruff: noqa: TCH004, F401 from __future__ import annotations from typing import TYPE_CHECKING, Any -from typing_extensions import TypeGuard +from litestar.utils import warn_deprecation -from litestar.exceptions import MissingDependencyException -from litestar.plugins import OpenAPISchemaPluginProtocol -from litestar.types import Empty -from litestar.typing import FieldDefinition -from litestar.utils import is_optional_union +__all__ = ("AttrsSchemaPlugin", "is_attrs_class") -try: - import attr - import attrs -except ImportError as e: - raise MissingDependencyException("attrs") from e -if TYPE_CHECKING: - from litestar._openapi.schema_generation import SchemaCreator - from litestar.openapi.spec import Schema - - -class AttrsSchemaPlugin(OpenAPISchemaPluginProtocol): - @staticmethod - def is_plugin_supported_type(value: Any) -> bool: - return is_attrs_class(value) or is_attrs_class(type(value)) - - def to_openapi_schema(self, field_definition: FieldDefinition, schema_creator: SchemaCreator) -> Schema: - """Given a type annotation, transform it into an OpenAPI schema class. +def __getattr__(attr_name: str) -> object: + if attr_name in __all__: + from litestar.plugins.attrs import AttrsSchemaPlugin, is_attrs_class - Args: - field_definition: FieldDefinition instance. - schema_creator: An instance of the schema creator class - - Returns: - An :class:`OpenAPI ` instance. - """ - - type_hints = field_definition.get_type_hints(include_extras=True, resolve_generics=True) - attr_fields = attr.fields_dict(field_definition.type_) - return schema_creator.create_component_schema( - field_definition, - required=sorted( - field_name - for field_name, attribute in attr_fields.items() - if attribute.default is attrs.NOTHING and not is_optional_union(type_hints[field_name]) - ), - property_fields={ - field_name: FieldDefinition.from_kwarg(type_hints[field_name], field_name) for field_name in attr_fields - }, + warn_deprecation( + deprecated_name=f"litestar.contrib.attrs.attrs_schema_plugin.{attr_name}", + version="2.12", + kind="import", + removal_in="3.0", + info=f"importing {attr_name} from 'litestar.contrib.attrs.attrs_schema_plugin' is deprecated, please " + f"import it from 'litestar.plugins.attrs' instead", ) + value = globals()[attr_name] = locals()[attr_name] + return value + raise AttributeError(f"module {__name__!r} has no attribute {attr_name!r}") # pragma: no cover -def is_attrs_class(annotation: Any) -> TypeGuard[type[attrs.AttrsInstance]]: # pyright: ignore - """Given a type annotation determine if the annotation is a class that includes an attrs attribute. - Args: - annotation: A type. - - Returns: - A typeguard determining whether the type is an attrs class. - """ - return attrs.has(annotation) if attrs is not Empty else False # type: ignore[comparison-overlap] +if TYPE_CHECKING: + from litestar.plugins.attrs import AttrsSchemaPlugin, is_attrs_class diff --git a/litestar/plugins/attrs.py b/litestar/plugins/attrs.py new file mode 100644 index 0000000000..22866d8e49 --- /dev/null +++ b/litestar/plugins/attrs.py @@ -0,0 +1,65 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from typing_extensions import TypeGuard + +from litestar.exceptions import MissingDependencyException +from litestar.plugins import OpenAPISchemaPluginProtocol +from litestar.types import Empty +from litestar.typing import FieldDefinition +from litestar.utils import is_optional_union + +try: + import attr + import attrs +except ImportError as e: + raise MissingDependencyException("attrs") from e + +if TYPE_CHECKING: + from litestar._openapi.schema_generation import SchemaCreator + from litestar.openapi.spec import Schema + +__all__ = ("AttrsSchemaPlugin","is_attrs_class") + +class AttrsSchemaPlugin(OpenAPISchemaPluginProtocol): + @staticmethod + def is_plugin_supported_type(value: Any) -> bool: + return is_attrs_class(value) or is_attrs_class(type(value)) + + def to_openapi_schema(self, field_definition: FieldDefinition, schema_creator: SchemaCreator) -> Schema: + """Given a type annotation, transform it into an OpenAPI schema class. + + Args: + field_definition: FieldDefinition instance. + schema_creator: An instance of the schema creator class + + Returns: + An :class:`OpenAPI ` instance. + """ + + type_hints = field_definition.get_type_hints(include_extras=True, resolve_generics=True) + attr_fields = attr.fields_dict(field_definition.type_) + return schema_creator.create_component_schema( + field_definition, + required=sorted( + field_name + for field_name, attribute in attr_fields.items() + if attribute.default is attrs.NOTHING and not is_optional_union(type_hints[field_name]) + ), + property_fields={ + field_name: FieldDefinition.from_kwarg(type_hints[field_name], field_name) for field_name in attr_fields + }, + ) + + +def is_attrs_class(annotation: Any) -> TypeGuard[type[attrs.AttrsInstance]]: # pyright: ignore + """Given a type annotation determine if the annotation is a class that includes an attrs attribute. + + Args: + annotation: A type. + + Returns: + A typeguard determining whether the type is an attrs class. + """ + return attrs.has(annotation) if attrs is not Empty else False # type: ignore[comparison-overlap] diff --git a/tests/unit/test_contrib/test_attrs.py b/tests/unit/test_contrib/test_attrs.py new file mode 100644 index 0000000000..0d3c1ac3a6 --- /dev/null +++ b/tests/unit/test_contrib/test_attrs.py @@ -0,0 +1,43 @@ +from __future__ import annotations + +import sys +import warnings +from importlib.util import cache_from_source +from pathlib import Path + +import pytest + +from litestar.contrib import attrs as contrib_attrs +from litestar.plugins import attrs as plugin_attrs + + +def purge_module(module_names: list[str], path: str | Path) -> None: + for name in module_names: + if name in sys.modules: + del sys.modules[name] + Path(cache_from_source(str(path))).unlink(missing_ok=True) + + +def test_contrib_attrs_deprecation_warning() -> None: + """Test that importing from contrib.attrs raises a deprecation warning.""" + purge_module(["litestar.contrib.attrs"], __file__) + with pytest.warns(DeprecationWarning) as warning_info: + from litestar.contrib.attrs import AttrsSchemaPlugin # noqa: F401 + + assert len(warning_info) == 1 + assert "litestar.contrib.attrs" in str(warning_info[0].message) + assert "Please use 'litestar.plugins.attrs' instead" in str(warning_info[0].message) + + +def test_plugin_attrs_no_warning() -> None: + """Test that importing from plugins.attrs does not raise a deprecation warning.""" + purge_module(["litestar.plugins.attrs"], __file__) + with warnings.catch_warnings(): + warnings.simplefilter("error") + from litestar.plugins.attrs import AttrsSchemaPlugin # noqa: F401 + + +def test_functionality_parity() -> None: + """Test that the functionality is identical between contrib and plugin versions.""" + assert contrib_attrs.AttrsSchemaPlugin is plugin_attrs.AttrsSchemaPlugin + assert contrib_attrs.is_attrs_class is plugin_attrs.is_attrs_class diff --git a/tests/unit/test_contrib/test_attrs/__init__.py b/tests/unit/test_plugins/test_attrs/__init__.py similarity index 100% rename from tests/unit/test_contrib/test_attrs/__init__.py rename to tests/unit/test_plugins/test_attrs/__init__.py diff --git a/tests/unit/test_contrib/test_attrs/test_inject_attrs_class.py b/tests/unit/test_plugins/test_attrs/test_inject_attrs_class.py similarity index 100% rename from tests/unit/test_contrib/test_attrs/test_inject_attrs_class.py rename to tests/unit/test_plugins/test_attrs/test_inject_attrs_class.py diff --git a/tests/unit/test_contrib/test_attrs/test_schema_plugin.py b/tests/unit/test_plugins/test_attrs/test_schema_plugin.py similarity index 94% rename from tests/unit/test_contrib/test_attrs/test_schema_plugin.py rename to tests/unit/test_plugins/test_attrs/test_schema_plugin.py index b83a3a5844..2d48aa128a 100644 --- a/tests/unit/test_contrib/test_attrs/test_schema_plugin.py +++ b/tests/unit/test_plugins/test_attrs/test_schema_plugin.py @@ -3,9 +3,9 @@ from attrs import define from typing_extensions import Annotated -from litestar.contrib.attrs.attrs_schema_plugin import AttrsSchemaPlugin from litestar.openapi.spec import OpenAPIType from litestar.openapi.spec.schema import Schema +from litestar.plugins.attrs import AttrsSchemaPlugin from litestar.typing import FieldDefinition from litestar.utils.helpers import get_name from tests.helpers import get_schema_for_field_definition diff --git a/tests/unit/test_contrib/test_attrs/test_schema_spec_generation.py b/tests/unit/test_plugins/test_attrs/test_schema_spec_generation.py similarity index 100% rename from tests/unit/test_contrib/test_attrs/test_schema_spec_generation.py rename to tests/unit/test_plugins/test_attrs/test_schema_spec_generation.py diff --git a/tests/unit/test_contrib/test_attrs/test_signature.py b/tests/unit/test_plugins/test_attrs/test_signature.py similarity index 100% rename from tests/unit/test_contrib/test_attrs/test_signature.py rename to tests/unit/test_plugins/test_attrs/test_signature.py diff --git a/tests/unit/test_plugins/test_base.py b/tests/unit/test_plugins/test_base.py index 5eec9644fb..c8d06658c8 100644 --- a/tests/unit/test_plugins/test_base.py +++ b/tests/unit/test_plugins/test_base.py @@ -7,8 +7,8 @@ from litestar import Litestar, MediaType, get from litestar.constants import UNDEFINED_SENTINELS -from litestar.contrib.attrs import AttrsSchemaPlugin from litestar.plugins import CLIPluginProtocol, InitPluginProtocol, OpenAPISchemaPlugin, PluginRegistry +from litestar.plugins.attrs import AttrsSchemaPlugin from litestar.plugins.core import MsgspecDIPlugin from litestar.plugins.pydantic import PydanticDIPlugin, PydanticInitPlugin, PydanticPlugin, PydanticSchemaPlugin from litestar.plugins.sqlalchemy import SQLAlchemySerializationPlugin From 4192577c5837c544faab50ceee1b3dd24a368f80 Mon Sep 17 00:00:00 2001 From: Cody Fincher Date: Sat, 16 Nov 2024 23:24:01 +0000 Subject: [PATCH 2/5] fix: tests --- litestar/plugins/attrs.py | 3 ++- tests/unit/test_contrib/test_attrs.py | 25 +++++++++++++------------ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/litestar/plugins/attrs.py b/litestar/plugins/attrs.py index 22866d8e49..831984f895 100644 --- a/litestar/plugins/attrs.py +++ b/litestar/plugins/attrs.py @@ -20,7 +20,8 @@ from litestar._openapi.schema_generation import SchemaCreator from litestar.openapi.spec import Schema -__all__ = ("AttrsSchemaPlugin","is_attrs_class") +__all__ = ("AttrsSchemaPlugin", "is_attrs_class") + class AttrsSchemaPlugin(OpenAPISchemaPluginProtocol): @staticmethod diff --git a/tests/unit/test_contrib/test_attrs.py b/tests/unit/test_contrib/test_attrs.py index 0d3c1ac3a6..7334f1a2dd 100644 --- a/tests/unit/test_contrib/test_attrs.py +++ b/tests/unit/test_contrib/test_attrs.py @@ -1,3 +1,4 @@ +# ruff: noqa: TCH004, F401 from __future__ import annotations import sys @@ -21,20 +22,20 @@ def purge_module(module_names: list[str], path: str | Path) -> None: def test_contrib_attrs_deprecation_warning() -> None: """Test that importing from contrib.attrs raises a deprecation warning.""" purge_module(["litestar.contrib.attrs"], __file__) - with pytest.warns(DeprecationWarning) as warning_info: - from litestar.contrib.attrs import AttrsSchemaPlugin # noqa: F401 + with pytest.warns( + DeprecationWarning, match="importing AttrsSchemaPlugin from 'litestar.contrib.attrs' is deprecated" + ) as warning_info: + from litestar.contrib.attrs import AttrsSchemaPlugin - assert len(warning_info) == 1 - assert "litestar.contrib.attrs" in str(warning_info[0].message) - assert "Please use 'litestar.plugins.attrs' instead" in str(warning_info[0].message) - -def test_plugin_attrs_no_warning() -> None: - """Test that importing from plugins.attrs does not raise a deprecation warning.""" - purge_module(["litestar.plugins.attrs"], __file__) - with warnings.catch_warnings(): - warnings.simplefilter("error") - from litestar.plugins.attrs import AttrsSchemaPlugin # noqa: F401 +def test_contrib_attrs_schema_deprecation_warning() -> None: + """Test that importing from contrib.attrs raises a deprecation warning.""" + purge_module(["litestar.contrib.attrs.attrs_schema_plugin"], __file__) + with pytest.warns( + DeprecationWarning, + match="importing AttrsSchemaPlugin from 'litestar.contrib.attrs.attrs_schema_plugin' is deprecated", + ) as warning_info: + from litestar.contrib.attrs.attrs_schema_plugin import AttrsSchemaPlugin def test_functionality_parity() -> None: From 465bfabe485ff96d887d86afbadf2aeb8503c56d Mon Sep 17 00:00:00 2001 From: Cody Fincher Date: Sat, 16 Nov 2024 23:27:41 +0000 Subject: [PATCH 3/5] fix: linting fixes --- tests/unit/test_contrib/test_attrs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/test_contrib/test_attrs.py b/tests/unit/test_contrib/test_attrs.py index 7334f1a2dd..ccd522fcb9 100644 --- a/tests/unit/test_contrib/test_attrs.py +++ b/tests/unit/test_contrib/test_attrs.py @@ -24,7 +24,7 @@ def test_contrib_attrs_deprecation_warning() -> None: purge_module(["litestar.contrib.attrs"], __file__) with pytest.warns( DeprecationWarning, match="importing AttrsSchemaPlugin from 'litestar.contrib.attrs' is deprecated" - ) as warning_info: + ): from litestar.contrib.attrs import AttrsSchemaPlugin @@ -34,7 +34,7 @@ def test_contrib_attrs_schema_deprecation_warning() -> None: with pytest.warns( DeprecationWarning, match="importing AttrsSchemaPlugin from 'litestar.contrib.attrs.attrs_schema_plugin' is deprecated", - ) as warning_info: + ): from litestar.contrib.attrs.attrs_schema_plugin import AttrsSchemaPlugin From 88eb9328274182521ccc321770155f301eccd6fc Mon Sep 17 00:00:00 2001 From: Cody Fincher Date: Sun, 17 Nov 2024 16:20:04 +0000 Subject: [PATCH 4/5] fix: correct deprecated version --- litestar/contrib/attrs/__init__.py | 2 +- litestar/contrib/attrs/attrs_schema_plugin.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/litestar/contrib/attrs/__init__.py b/litestar/contrib/attrs/__init__.py index f6490eee30..a9da285b6b 100644 --- a/litestar/contrib/attrs/__init__.py +++ b/litestar/contrib/attrs/__init__.py @@ -14,7 +14,7 @@ def __getattr__(attr_name: str) -> object: warn_deprecation( deprecated_name=f"litestar.contrib.attrs.{attr_name}", - version="2.12", + version="2.12.1", kind="import", removal_in="3.0", info=f"importing {attr_name} from 'litestar.contrib.attrs' is deprecated, please " diff --git a/litestar/contrib/attrs/attrs_schema_plugin.py b/litestar/contrib/attrs/attrs_schema_plugin.py index 3f88cd787d..ca56804f16 100644 --- a/litestar/contrib/attrs/attrs_schema_plugin.py +++ b/litestar/contrib/attrs/attrs_schema_plugin.py @@ -14,7 +14,7 @@ def __getattr__(attr_name: str) -> object: warn_deprecation( deprecated_name=f"litestar.contrib.attrs.attrs_schema_plugin.{attr_name}", - version="2.12", + version="2.12.1", kind="import", removal_in="3.0", info=f"importing {attr_name} from 'litestar.contrib.attrs.attrs_schema_plugin' is deprecated, please " From 6fb06298312e18ce97d2395de64818a5ef74b8dc Mon Sep 17 00:00:00 2001 From: Cody Fincher Date: Wed, 20 Nov 2024 16:31:27 +0000 Subject: [PATCH 5/5] fix: bump deprecated version --- litestar/contrib/attrs/__init__.py | 2 +- litestar/contrib/attrs/attrs_schema_plugin.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/litestar/contrib/attrs/__init__.py b/litestar/contrib/attrs/__init__.py index a9da285b6b..1b27019c63 100644 --- a/litestar/contrib/attrs/__init__.py +++ b/litestar/contrib/attrs/__init__.py @@ -14,7 +14,7 @@ def __getattr__(attr_name: str) -> object: warn_deprecation( deprecated_name=f"litestar.contrib.attrs.{attr_name}", - version="2.12.1", + version="2.13.0", kind="import", removal_in="3.0", info=f"importing {attr_name} from 'litestar.contrib.attrs' is deprecated, please " diff --git a/litestar/contrib/attrs/attrs_schema_plugin.py b/litestar/contrib/attrs/attrs_schema_plugin.py index ca56804f16..05cc4a8c48 100644 --- a/litestar/contrib/attrs/attrs_schema_plugin.py +++ b/litestar/contrib/attrs/attrs_schema_plugin.py @@ -14,7 +14,7 @@ def __getattr__(attr_name: str) -> object: warn_deprecation( deprecated_name=f"litestar.contrib.attrs.attrs_schema_plugin.{attr_name}", - version="2.12.1", + version="2.13.0", kind="import", removal_in="3.0", info=f"importing {attr_name} from 'litestar.contrib.attrs.attrs_schema_plugin' is deprecated, please "