Skip to content

Commit

Permalink
feat: rework and simplify DTOs (#2088)
Browse files Browse the repository at this point in the history
* feat(internal): reworked DTO logic

* feat(internal): cleanup listeners

* chore(compat): python 3.8 fixes

* feat: update plugins access to use registry

* fix(dto): resolve issue 1929

* Update tests/unit/test_contrib/test_sqlalchemy/test_serialization_plugin.py

* Update tests/unit/test_contrib/test_sqlalchemy/test_serialization_plugin.py

* chore(dead code): cleanup

* Update litestar/dto/base_dto.py

Co-authored-by: Janek Nouvertné <25355197+provinzkraut@users.noreply.github.com>

* chore: address review comments

* Update litestar/handlers/base.py

Co-authored-by: Janek Nouvertné <25355197+provinzkraut@users.noreply.github.com>

* chore: address review comments

* chore: make handler_id unique

---------

Co-authored-by: Janek Nouvertné <25355197+provinzkraut@users.noreply.github.com>
Co-authored-by: Cody Fincher <204685+cofin@users.noreply.github.com>
  • Loading branch information
3 people authored Aug 3, 2023
1 parent fc6ed28 commit b9814c2
Show file tree
Hide file tree
Showing 100 changed files with 1,551 additions and 2,256 deletions.
505 changes: 119 additions & 386 deletions .all-contributorsrc

Large diffs are not rendered by default.

20 changes: 10 additions & 10 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,19 @@ repos:
- id: end-of-file-fixer
- id: mixed-line-ending
- id: trailing-whitespace
# - repo: https://github.com/python-poetry/poetry
# rev: "1.5.0"
# hooks:
# - id: poetry-check
# - id: poetry-lock
# args: ["--no-update"]
- repo: https://github.com/python-poetry/poetry
rev: "1.5.0"
hooks:
- id: poetry-check
- id: poetry-lock
args: ["--no-update"]
- repo: https://github.com/provinzkraut/unasyncd
rev: "v0.6.0"
hooks:
- id: unasyncd
additional_dependencies: ["ruff"]
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: "v0.0.280"
rev: "v0.0.282"
hooks:
- id: ruff
args: ["--fix"]
Expand All @@ -48,7 +48,7 @@ repos:
hooks:
- id: blacken-docs
- repo: https://github.com/pre-commit/mirrors-prettier
rev: "v3.0.0"
rev: "v3.0.1"
hooks:
- id: prettier
exclude: "_templates|.git"
Expand All @@ -59,7 +59,7 @@ repos:
exclude: "test*|examples*|tools"
args: ["--use-tuple"]
- repo: https://github.com/ariebovenberg/slotscheck
rev: v0.16.5
rev: v0.17.0
hooks:
- id: slotscheck
exclude: "test_*|docs"
Expand Down Expand Up @@ -123,7 +123,7 @@ repos:
time-machine,
]
- repo: https://github.com/RobertCraigie/pyright-python
rev: v1.1.318
rev: v1.1.320
hooks:
- id: pyright
exclude: "test_apps|tools|docs|_openapi|tests/examples|tests/docker_service_fixtures|tests/unit/test_partial"
Expand Down
13 changes: 8 additions & 5 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,25 +106,28 @@
(PY_CLASS, "litestar.contrib.sqlalchemy.repository.SelectT"),
(PY_OBJ, "litestar.security.base.AuthType"),
# intentionally undocumented
(PY_CLASS, "BacklogStrategy"),
(PY_CLASS, "ExceptionT"),
(PY_CLASS, "NoneType"),
(PY_CLASS, "litestar._signature.field.FieldDefinition"),
(PY_CLASS, "litestar.utils.signature.FieldDefinition"),
(PY_CLASS, "litestar._openapi.schema_generation.schema.SchemaCreator"),
(PY_CLASS, "litestar.utils.signature.ParsedSignature"),
(PY_CLASS, "litestar.utils.sync.AsyncCallable"),
(PY_CLASS, "BacklogStrategy"),
(PY_CLASS, "ExceptionT"),
(PY_CLASS, "litestar._signature.model.SignatureModel"),
# types in changelog that no longer exist
(PY_ATTR, "litestar.dto.factory.DTOConfig.underscore_fields_private"),
(PY_CLASS, "anyio.abc.BlockingPortal"),
(PY_CLASS, "litestar.contrib.msgspec.MsgspecDTO"),
(PY_CLASS, "litestar.dto.base_factory.AbstractDTOFactory"),
(PY_CLASS, "litestar.dto.factory.DTOConfig"),
(PY_CLASS, "litestar.dto.factory.DTOData"),
(PY_CLASS, "litestar.dto.interface.DTOInterface"),
(PY_CLASS, "litestar.partial.Partial"),
(PY_CLASS, "litestar.response.RedirectResponse"),
(PY_CLASS, "litestar.response_containers.Redirect"),
(PY_CLASS, "litestar.response_containers.Template"),
(PY_CLASS, "litestar.typing.ParsedType"),
(PY_CLASS, "litestar.partial.Partial"),
(PY_METH, "litestar.dto.factory.DTOData.create_instance"),
(PY_METH, "litestar.dto.interface.DTOInterface.data_to_encodable_type"),
]

nitpick_ignore_regex = [
Expand Down
5 changes: 5 additions & 0 deletions docs/reference/dto/base_dto.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
base_dto
========

.. automodule:: litestar.dto.base_dto
:members:
5 changes: 0 additions & 5 deletions docs/reference/dto/base_factory.rst

This file was deleted.

5 changes: 5 additions & 0 deletions docs/reference/dto/dataclass_dto.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
dataclass_dto
=============

.. automodule:: litestar.dto.dataclass_dto
:members:
5 changes: 0 additions & 5 deletions docs/reference/dto/dataclass_dto_factory.rst

This file was deleted.

7 changes: 3 additions & 4 deletions docs/reference/dto/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ dto
.. toctree::
:maxdepth: 1

interface
config
data_structures
field
types
base_factory
msgspec_dto_factory
dataclass_dto_factory
base_dto
msgspec_dto
dataclass_dto
5 changes: 0 additions & 5 deletions docs/reference/dto/interface.rst

This file was deleted.

5 changes: 5 additions & 0 deletions docs/reference/dto/msgspec_dto.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
msgspec_dto
===========

.. automodule:: litestar.dto.msgspec_dto
:members:
5 changes: 0 additions & 5 deletions docs/reference/dto/msgspec_dto_factory.rst

This file was deleted.

2 changes: 1 addition & 1 deletion docs/release-notes/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@
``data.create_instance(foo__bar="baz")``.

.. seealso::
:ref:`usage/dto/1-dto-factory:Providing values for nested data`
:ref:`usage/dto/1-abstract-dto:Providing values for nested data`

.. change:: DTOs: Hybrid properties and association proxies in ``SQLAlchemyDTO``
:type: feature
Expand Down
12 changes: 5 additions & 7 deletions docs/release-notes/whats-new-2.rst
Original file line number Diff line number Diff line change
Expand Up @@ -441,18 +441,16 @@ DTOs
DTOs are now defined using the ``dto`` and ``return_dto`` arguments to
handlers/controllers/routers and the application.

A DTO is any type that conforms to the :class:`litestar.dto.interface.DTOInterface`
protocol.
A DTO is any type that inherits from :class:`litestar.dto.base_dto.AbstractDTO`.

Litestar provides a suite of factory types that implement the ``DTOInterface`` protocol
Litestar provides a suite of types that implement the ``AbstractDTO`` abstract class
and can be used to define DTOs:

- :class:`litestar.dto.dataclass_dto_factory.DataclassDTO`
- :class:`litestar.dto.msgspec_dto_factory.MsgspecDTO`
- :class:`litestar.dto.dataclass_dto.DataclassDTO`
- :class:`litestar.dto.msgspec_dto.MsgspecDTO`
- :class:`litestar.contrib.sqlalchemy.dto.SQLAlchemyDTO`
- :class:`litestar.contrib.pydantic.PydanticDTO`
- ``litestar.contrib.piccolo.PiccoloDTO`` (TODO)
- ``litestar.contrib.tortoise.TortoiseDTO`` (TODO)
- :class:`litestar.contrib.piccolo.PiccoloDTO`

For example, to define a DTO from a dataclass:

Expand Down
2 changes: 1 addition & 1 deletion docs/tutorials/dto-tutorial/01-simple-dto-exclude.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ The :class:`DTOConfig <litestar.dto.config.DTOConfig>` class is used to configur
exclude the ``email`` field from the DTO, but there are many other configuration options available and we'll cover most
of them in this tutorial.

The :class:`DataclassDTO <litestar.dto.dataclass_dto_factory.DataclassDTO>` class is a factory class that specializes
The :class:`DataclassDTO <litestar.dto.dataclass_dto.DataclassDTO>` class is a factory class that specializes
in creating DTOs from dataclasses. It is also a :class:`Generic <typing.Generic>` class, which means that it it accepts
a type parameter. When we provide a type parameter to a generic class it makes that class a specialized version of the
generic class. In this case, we create a DTO type that specializes in transferring data to and from instances of the
Expand Down
2 changes: 1 addition & 1 deletion docs/usage/dto/0-basic-use.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ that will take responsibility for the data received and returned from handlers:
provided, the DTO described by the ``dto`` parameter is used.

The object provided to both of these parameters must be a class that conforms to the
:class:`DTOInterface <litestar.dto.interface.DTOInterface>` protocol.
:class:`AbstractDTO <litestar.dto.base_dto.AbstractDTO>` protocol.

Defining DTOs on handlers
~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
DTO Factory
AbstractDTO
===========

Litestar maintains a suite of DTO factory types that can be used to create DTOs for use with popular data modelling
libraries, such as ORMs. These take a model type as a generic type argument, and create subtypes of
:class:`AbstractDTOFactory <litestar.dto.base_factory.AbstractDTOFactory>` that support conversion of that model type to
:class:`AbstractDTO <litestar.dto.base_dto.AbstractDTO>` that support conversion of that model type to
and from raw bytes.

The following factories are currently available:

- :class:`DataclassDTO <litestar.dto.dataclass_dto_factory.DataclassDTO>`
- :class:`MsgspecDTO <litestar.dto.msgspec_dto_factory.MsgspecDTO>`
- :class:`DataclassDTO <litestar.dto.dataclass_dto.DataclassDTO>`
- :class:`MsgspecDTO <litestar.dto.msgspec_dto.MsgspecDTO>`
- :class:`PydanticDTO <litestar.contrib.pydantic.PydanticDTO>`
- :class:`SQLAlchemyDTO <litestar.contrib.sqlalchemy.dto.SQLAlchemyDTO>`

Expand Down
26 changes: 26 additions & 0 deletions docs/usage/dto/2-creating-custom-dto-classes.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
Implementing Cusotm DTO Classes
===============================

While Litestar maintains a suite of DTO factories, it is possible to create your own DTOs. To do so, you must implement
the :class:`AbstractDTO <litestar.dto.base_dto.AbstractDTO>` abc.

The following is a description of the methods of the protocol, and how they are used by Litestar. For detailed
information on the signature of each method, see the :class:`reference docs <litestar.dto.base_dto.AbstractDTO>`.

Abstract Methods
~~~~~~~~~~~~~~~~

These methods must be implemented on any :class:`AbstractDTO <litestar.dto.base_dto.AbstractDTO>` subtype.

``generate_field_definitions``
------------------------------

This method receives the model type for the DTO and it should return a generator yielding
:class:`DTOFieldDefinition<litestar.dto.data_structures.DTOFieldDefinition>` instances corresponding with
the model fields.

``detect_nested_field``
-----------------------

This method receives a :class:`FieldDefinition<litestar.typing.FieldDefinition>` instance it it should return a boolean
indicating whether the field is a nested model field.
71 changes: 0 additions & 71 deletions docs/usage/dto/2-dto-interface.rst

This file was deleted.

4 changes: 2 additions & 2 deletions docs/usage/dto/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -79,5 +79,5 @@ Contents
.. toctree::

0-basic-use
1-dto-factory
2-dto-interface
1-abstract-dto
2-creating-custom-dto-classes
6 changes: 3 additions & 3 deletions docs/usage/plugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,11 @@ Implementations of these plugins must define the following methods.
The method takes a :class:`FieldDefinition <litestar.typing.FieldDefinition>` instance as an argument and returns a :class:`bool`
indicating whether the plugin supports serialization for that type.

:meth:`create_dto_for_type(self, field_definition: FieldDefinition) -> type[DTOInterface]: <litestar.plugins.SerializationPluginProtocol.create_dto_for_type>`
:meth:`create_dto_for_type(self, field_definition: FieldDefinition) -> type[AbstractDTO]: <litestar.plugins.SerializationPluginProtocol.create_dto_for_type>`
--------------------------------------------------------------------------------------------------------------------------------------------------------------

This method accepts a :class:`FieldDefinition <litestar.typing.FieldDefinition>` instance as an argument and must return a
:class:`DTOInterface <litestar.dto.interface.DTOInterface>` implementation that can be used to serialize and deserialize
:class:`AbstractDTO <litestar.dto.base_dto.AbstractDTO>` implementation that can be used to serialize and deserialize
the type.

During application startup, if a data or return annotation is encountered that is not a supported type, is supported by
Expand All @@ -92,7 +92,7 @@ The following example shows the actual implementation of the ``SerializationPlug
returns a :class:`bool` indicating whether the plugin supports serialization for the given type. Specifically, we return
``True`` if the parsed type is either a collection of SQLAlchemy models or a single SQLAlchemy model.

:meth:`create_dto_for_type(self, field_definition: FieldDefinition) -> type[DTOInterface]: <litestar.contrib.sqlalchemy.plugins.SQLAlchemySerializationPlugin.create_dto_for_type>`
:meth:`create_dto_for_type(self, field_definition: FieldDefinition) -> type[AbstractDTO]: <litestar.contrib.sqlalchemy.plugins.SQLAlchemySerializationPlugin.create_dto_for_type>`
takes a :class:`FieldDefinition <litestar.typing.FieldDefinition>` instance as an argument and returns a
:class:`SQLAlchemyDTO <litestar.contrib.sqlalchemy.dto.SQLAlchemyDTO>` subclass and includes some logic that may be
interesting to potential serialization plugin authors.
Expand Down
3 changes: 1 addition & 2 deletions litestar/_kwargs/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from inspect import isasyncgen, isgenerator
from typing import TYPE_CHECKING, Any

from litestar._signature import get_signature_model
from litestar.utils.compat import async_next

__all__ = ("Dependency", "create_dependency_batches", "map_dependencies_recursively", "resolve_dependency")
Expand Down Expand Up @@ -58,7 +57,7 @@ async def resolve_dependency(
kwargs: Any kwargs to pass to the dependency, the result will be stored here as well.
cleanup_group: DependencyCleanupGroup to which generators returned by ``dependency`` will be added
"""
signature_model = get_signature_model(dependency.provide)
signature_model = dependency.provide.signature_model
dependency_kwargs = (
signature_model.parse_values_from_connection_kwargs(connection=connection, **kwargs)
if signature_model._fields
Expand Down
Loading

0 comments on commit b9814c2

Please sign in to comment.