Skip to content

Commit

Permalink
Merge branch 'main' into fix-resource-warnings
Browse files Browse the repository at this point in the history
  • Loading branch information
cofin authored Nov 29, 2024
2 parents 8cf963a + 22d8f18 commit 1e15a22
Show file tree
Hide file tree
Showing 25 changed files with 224 additions and 192 deletions.
30 changes: 8 additions & 22 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@

extensions = [
"sphinx.ext.intersphinx",
"sphinx.ext.autosectionlabel",
"sphinx.ext.autodoc",
"sphinx.ext.napoleon",
"sphinx.ext.autosectionlabel",
"sphinx_design",
"auto_pytabs.sphinx_ext",
"tools.sphinx_ext",
Expand All @@ -48,6 +48,7 @@
"msgspec": ("https://jcristharif.com/msgspec/", None),
"anyio": ("https://anyio.readthedocs.io/en/stable/", None),
"multidict": ("https://multidict.aio-libs.org/en/stable/", None),
"cryptography": ("https://cryptography.io/en/latest/", None),
"sqlalchemy": ("https://docs.sqlalchemy.org/en/20/", None),
"alembic": ("https://alembic.sqlalchemy.org/en/latest/", None),
"click": ("https://click.palletsprojects.com/en/8.1.x/", None),
Expand All @@ -60,6 +61,8 @@
"advanced-alchemy": ("https://docs.advanced-alchemy.litestar.dev/latest/", None),
"jinja2": ("https://jinja.palletsprojects.com/en/latest/", None),
"trio": ("https://trio.readthedocs.io/en/stable/", None),
"pydantic": ("https://docs.pydantic.dev/latest/", None),
"typing_extensions": ("https://typing-extensions.readthedocs.io/en/stable/", None),
}

napoleon_google_docstring = True
Expand All @@ -74,6 +77,7 @@
autodoc_default_options = {"special-members": "__init__", "show-inheritance": True, "members": True}
autodoc_member_order = "bysource"
autodoc_typehints_format = "short"
autodoc_mock_imports = []

nitpicky = True
nitpick_ignore = [
Expand Down Expand Up @@ -164,27 +168,7 @@
(PY_METH, "litestar.dto.factory.DTOData.create_instance"),
(PY_METH, "litestar.dto.interface.DTOInterface.data_to_encodable_type"),
(PY_CLASS, "MetaData"),
(PY_CLASS, "advanced_alchemy.repository.typing.ModelT"),
(PY_OBJ, "advanced_alchemy.config.common.SessionMakerT"),
(PY_OBJ, "advanced_alchemy.config.common.ConnectionT"),
(PY_CLASS, "advanced_alchemy.extensions.litestar.plugins._slots_base.SlotsBase"),
(PY_CLASS, "advanced_alchemy.config.EngineConfig"),
(PY_CLASS, "advanced_alchemy.config.common.GenericAlembicConfig"),
(PY_CLASS, "advanced_alchemy.extensions.litestar.SQLAlchemyDTO"),
(PY_CLASS, "advanced_alchemy.extensions.litestar.dto.SQLAlchemyDTO"),
(PY_CLASS, "advanced_alchemy.extensions.litestar.plugins.SQLAlchemyPlugin"),
(PY_CLASS, "advanced_alchemy.extensions.litestar.plugins.SQLAlchemySerializationPlugin"),
(PY_CLASS, "advanced_alchemy.extensions.litestar.plugins.SQLAlchemyInitPlugin"),
(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"),
(PY_CLASS, "advanced_alchemy.types.BigIntIdentity"),
(PY_FUNC, "sqlalchemy.get_engine"),
(PY_ATTR, "advanced_alchemy.repository.AbstractAsyncRepository.id_attribute"),
(PY_OBJ, "litestar.template.base.T_co"),
("py:exc", "RepositoryError"),
("py:exc", "InternalServerError"),
Expand All @@ -204,6 +188,9 @@
(PY_CLASS, "typing.Self"),
(PY_CLASS, "attr.AttrsInstance"),
(PY_CLASS, "typing_extensions.TypeGuard"),
(PY_CLASS, "advanced_alchemy.types.BigIntIdentity"),
(PY_CLASS, "advanced_alchemy.types.JsonB"),
(PY_CLASS, "advanced_alchemy.repository.SQLAlchemyAsyncRepository"),
]

nitpick_ignore_regex = [
Expand Down Expand Up @@ -247,7 +234,6 @@
"litestar.template": {"litestar.template.base.T_co"},
"litestar.openapi.OpenAPIController.security": {"SecurityRequirement"},
"litestar.response.file.async_file_iterator": {"FileSystemAdapter"},
"advanced_alchemy._listeners.touch_updated_timestamp": {"Session"},
re.compile("litestar.response.redirect.*"): {"RedirectStatusType"},
re.compile(r"litestar\.plugins.*"): re.compile(".*ModelT"),
re.compile(r"litestar\.(contrib|repository)\.*"): re.compile(".*T"),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from typing import AsyncGenerator, List, Optional

from advanced_alchemy.extensions.litestar.plugins.init.config.asyncio import autocommit_before_send_handler
from sqlalchemy import select
from sqlalchemy.exc import IntegrityError, NoResultFound
from sqlalchemy.ext.asyncio import AsyncSession
Expand Down Expand Up @@ -74,7 +73,7 @@ async def update_item(item_title: str, data: TodoItem, transaction: AsyncSession
connection_string="sqlite+aiosqlite:///todo.sqlite",
metadata=Base.metadata,
create_all=True,
before_send_handler=autocommit_before_send_handler,
before_send_handler="autocommit",
)

app = Litestar(
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/openapi/plugins/swagger_ui_config.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from litestar.openapi.plugins import SwaggerRenderPlugin

swagger_plugin = SwaggerRenderPlugin(version="5.1.3", path="/swagger")
swagger_plugin = SwaggerRenderPlugin(version="5.18.2", path="/swagger")
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ Additional features provided by the built-in base models include:
reverts to an ``Integer`` for unsupported variants.
- A custom :class:`JsonB <advanced_alchemy.types.JsonB>` type that uses
native ``JSONB`` where possible and ``Binary`` or ``Blob`` as an alternative.
- A custom :class:`EncryptedString <advanced_alchemy.types.EncryptedString>` encrypted string that supports multiple cryptography backends.

Let's build on this as we look at the repository classes.

Expand Down
2 changes: 1 addition & 1 deletion docs/tutorials/sqlalchemy/4-final-touches-and-recap.rst
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ engine and session lifecycle, and register our ``transaction`` dependency.
.. literalinclude:: /examples/contrib/sqlalchemy/plugins/tutorial/full_app_with_plugin.py
:language: python
:linenos:
:lines: 80-84
:lines: 80-83

.. seealso::

Expand Down
28 changes: 14 additions & 14 deletions docs/usage/databases/sqlalchemy/plugins/sqlalchemy_init_plugin.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
SQLAlchemy Init Plugin
----------------------

The :class:`SQLAlchemyInitPlugin <advanced_alchemy.extensions.litestar.plugins.SQLAlchemyInitPlugin>` adds functionality to the
The :class:`SQLAlchemyInitPlugin <advanced_alchemy.extensions.litestar.SQLAlchemyInitPlugin>` adds functionality to the
application that supports using Litestar with `SQLAlchemy <http://www.sqlalchemy.org/>`_.

The plugin:
Expand Down Expand Up @@ -39,8 +39,8 @@ Renaming the dependencies
#########################

You can change the name that the engine and session are bound to by setting the
:attr:`engine_dependency_key <advanced_alchemy.extensions.litestar.plugins.init.config.asyncio.SQLAlchemyAsyncConfig.engine_dependency_key>`
and :attr:`session_dependency_key <advanced_alchemy.extensions.litestar.plugins.init.config.asyncio.SQLAlchemyAsyncConfig.session_dependency_key>`
:attr:`engine_dependency_key <advanced_alchemy.extensions.litestar.SQLAlchemyAsyncConfig.engine_dependency_key>`
and :attr:`session_dependency_key <advanced_alchemy.extensions.litestar.SQLAlchemyAsyncConfig.session_dependency_key>`
attributes on the plugin configuration.

Configuring the before send handler
Expand All @@ -50,7 +50,7 @@ The plugin configures a ``before_send`` handler that is called before sending a
session and removes it from the connection scope.

You can change the handler by setting the
:attr:`before_send_handler <advanced_alchemy.extensions.litestar.plugins.init.config.asyncio.SQLAlchemyAsyncConfig.before_send_handler>`
:attr:`before_send_handler <advanced_alchemy.extensions.litestar.SQLAlchemyAsyncConfig.before_send_handler>`
attribute on the configuration object. For example, an alternate handler is available that will also commit the session
on success and rollback upon failure.

Expand All @@ -73,21 +73,21 @@ on success and rollback upon failure.
Configuring the plugins
#######################

Both the :class:`SQLAlchemyAsyncConfig <advanced_alchemy.extensions.litestar.config.SQLAlchemyAsyncConfig>` and the
:class:`SQLAlchemySyncConfig <advanced_alchemy.extensions.litestar.config.SQLAlchemySyncConfig>` have an ``engine_config``
Both the :class:`SQLAlchemyAsyncConfig <advanced_alchemy.extensions.litestar.SQLAlchemyAsyncConfig>` and the
:class:`SQLAlchemySyncConfig <advanced_alchemy.extensions.litestar.SQLAlchemySyncConfig>` have an ``engine_config``
attribute that is used to configure the engine. The ``engine_config`` attribute is an instance of
:class:`EngineConfig <advanced_alchemy.config.EngineConfig>` and exposes all of the configuration options
:class:`EngineConfig <advanced_alchemy.extensions.litestar.EngineConfig>` and exposes all of the configuration options
available to the SQLAlchemy engine.

The :class:`SQLAlchemyAsyncConfig <advanced_alchemy.extensions.litestar.config.SQLAlchemyAsyncConfig>` class and the
:class:`SQLAlchemySyncConfig <advanced_alchemy.extensions.litestar.config.SQLAlchemySyncConfig>` class also have a
The :class:`SQLAlchemyAsyncConfig <advanced_alchemy.extensions.litestar.SQLAlchemyAsyncConfig>` class and the
:class:`SQLAlchemySyncConfig <advanced_alchemy.extensions.litestar.SQLAlchemySyncConfig>` class also have a
``session_config`` attribute that is used to configure the session. This is either an instance of
:class:`AsyncSessionConfig <advanced_alchemy.config.AsyncSessionConfig>` or
:class:`SyncSessionConfig <advanced_alchemy.config.SyncSessionConfig>` depending on the type of config
:class:`AsyncSessionConfig <advanced_alchemy.extensions.litestar.AsyncSessionConfig>` or
:class:`SyncSessionConfig <advanced_alchemy.extensions.litestar.SyncSessionConfig>` depending on the type of config
object. These classes expose all of the configuration options available to the SQLAlchemy session.

Finally, the :class:`SQLAlchemyAsyncConfig <advanced_alchemy.extensions.litestar.config.SQLAlchemyAsyncConfig>` class and the
:class:`SQLAlchemySyncConfig <advanced_alchemy.extensions.litestar.config.SQLAlchemySyncConfig>` class expose configuration
Finally, the :class:`SQLAlchemyAsyncConfig <advanced_alchemy.extensions.litestar.SQLAlchemyAsyncConfig>` class and the
:class:`SQLAlchemySyncConfig <advanced_alchemy.extensions.litestar.SQLAlchemySyncConfig>` class expose configuration
options to control their behavior.

Consult the reference documentation for more information.
Expand All @@ -98,7 +98,7 @@ Example
The below example is a complete demonstration of use of the init plugin. Readers who are familiar with the prior section
may note the additional complexity involved in managing the conversion to and from SQLAlchemy objects within the
handlers. Read on to see how this increased complexity is efficiently handled by the
:class:`SQLAlchemySerializationPlugin <advanced_alchemy.extensions.litestar.plugins.SQLAlchemySerializationPlugin>`.
:class:`SQLAlchemySerializationPlugin <advanced_alchemy.extensions.litestar.SQLAlchemySerializationPlugin>`.

.. tab-set::

Expand Down
10 changes: 5 additions & 5 deletions docs/usage/databases/sqlalchemy/plugins/sqlalchemy_plugin.rst
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
SQLAlchemy Plugin
-----------------

The :class:`SQLAlchemyPlugin <advanced_alchemy.extensions.litestar.plugins.SQLAlchemyPlugin>` provides complete support for
The :class:`SQLAlchemyPlugin <advanced_alchemy.extensions.litestar.SQLAlchemyPlugin>` provides complete support for
working with `SQLAlchemy <https://www.sqlalchemy.org/>`_ in Litestar applications.

.. note::

This plugin is only compatible with SQLAlchemy 2.0+.

The :class:`SQLAlchemyPlugin <advanced_alchemy.extensions.litestar.plugins.SQLAlchemyPlugin>` combines the functionality of
:class:`SQLAlchemyInitPlugin <advanced_alchemy.extensions.litestar.plugins.SQLAlchemyInitPlugin>` and
:class:`SQLAlchemySerializationPlugin <advanced_alchemy.extensions.litestar.plugins.SQLAlchemySerializationPlugin>`, each of
The :class:`SQLAlchemyPlugin <advanced_alchemy.extensions.litestar.SQLAlchemyPlugin>` combines the functionality of
:class:`SQLAlchemyInitPlugin <advanced_alchemy.extensions.litestar.SQLAlchemyInitPlugin>` and
:class:`SQLAlchemySerializationPlugin <advanced_alchemy.extensions.litestar.SQLAlchemySerializationPlugin>`, each of
which are examined in detail in the following sections. As such, this section describes a complete example of using the
:class:`SQLAlchemyPlugin <advanced_alchemy.extensions.litestar.plugins.SQLAlchemyPlugin>` with a Litestar application and a
:class:`SQLAlchemyPlugin <advanced_alchemy.extensions.litestar.SQLAlchemyPlugin>` with a Litestar application and a
SQLite database.

Or, skip ahead to :doc:`/usage/databases/sqlalchemy/plugins/sqlalchemy_init_plugin` or
Expand Down
4 changes: 2 additions & 2 deletions docs/usage/plugins/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,11 @@ The following example shows the actual implementation of the ``SerializationPlug
:language: python
:caption: ``SerializationPluginProtocol`` implementation example

:meth:`supports_type(self, field_definition: FieldDefinition) -> bool: <advanced_alchemy.extensions.litestar.plugins.serialization.SQLAlchemySerializationPlugin.supports_type>`
:meth:`supports_type(self, field_definition: FieldDefinition) -> bool: <advanced_alchemy.extensions.litestar.SQLAlchemySerializationPlugin.supports_type>`
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[AbstractDTO]: <advanced_alchemy.extensions.litestar.plugins.SQLAlchemySerializationPlugin.create_dto_for_type>`
:meth:`create_dto_for_type(self, field_definition: FieldDefinition) -> type[AbstractDTO]: <advanced_alchemy.extensions.litestar.SQLAlchemySerializationPlugin.create_dto_for_type>`
takes a :class:`FieldDefinition <litestar.typing.FieldDefinition>` instance as an argument and returns a
:class:`SQLAlchemyDTO <advanced_alchemy.extensions.litestar.dto.SQLAlchemyDTO>` subclass and includes some logic that may be
interesting to potential serialization plugin authors.
Expand Down
64 changes: 40 additions & 24 deletions litestar/_openapi/schema_generation/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from copy import copy
from datetime import date, datetime, time, timedelta
from decimal import Decimal
from enum import Enum, EnumMeta
from enum import Enum
from ipaddress import IPv4Address, IPv4Interface, IPv4Network, IPv6Address, IPv6Interface, IPv6Network
from pathlib import Path
from typing import (
Expand Down Expand Up @@ -40,9 +40,7 @@
create_string_constrained_field_schema,
)
from litestar._openapi.schema_generation.utils import (
_should_create_enum_schema,
_should_create_literal_schema,
_type_or_first_not_none_inner_type,
get_json_schema_formatted_examples,
)
from litestar.datastructures import SecretBytes, SecretString, UploadFile
Expand Down Expand Up @@ -181,22 +179,6 @@ def _get_type_schema_name(field_definition: FieldDefinition) -> str:
return name


def create_enum_schema(annotation: EnumMeta, include_null: bool = False) -> Schema:
"""Create a schema instance for an enum.
Args:
annotation: An enum.
include_null: Whether to include null as a possible value.
Returns:
A schema instance.
"""
enum_values: list[str | int | None] = [v.value for v in annotation] # type: ignore[var-annotated]
if include_null and None not in enum_values:
enum_values.append(None)
return Schema(type=_types_in_list(enum_values), enum=enum_values)


def _iter_flat_literal_args(annotation: Any) -> Iterable[Any]:
"""Iterate over the flattened arguments of a Literal.
Expand Down Expand Up @@ -331,18 +313,20 @@ def for_field_definition(self, field_definition: FieldDefinition) -> Schema | Re
result = self.for_type_alias_type(field_definition)
elif plugin_for_annotation := self.get_plugin_for(field_definition):
result = self.for_plugin(field_definition, plugin_for_annotation)
elif _should_create_enum_schema(field_definition):
annotation = _type_or_first_not_none_inner_type(field_definition)
result = create_enum_schema(annotation, include_null=field_definition.is_optional)
elif _should_create_literal_schema(field_definition):
annotation = (
make_non_optional_union(field_definition.annotation)
if field_definition.is_optional
else field_definition.annotation
)
result = create_literal_schema(annotation, include_null=field_definition.is_optional)
result = create_literal_schema(
annotation,
include_null=field_definition.is_optional,
)
elif field_definition.is_optional:
result = self.for_optional_field(field_definition)
elif field_definition.is_enum:
result = self.for_enum_field(field_definition)
elif field_definition.is_union:
result = self.for_union_field(field_definition)
elif field_definition.is_type_var:
Expand Down Expand Up @@ -445,7 +429,7 @@ def for_optional_field(self, field_definition: FieldDefinition) -> Schema:
else:
result = [schema_or_reference]

return Schema(one_of=[Schema(type=OpenAPIType.NULL), *result])
return Schema(one_of=[*result, Schema(type=OpenAPIType.NULL)])

def for_union_field(self, field_definition: FieldDefinition) -> Schema:
"""Create a Schema for a union FieldDefinition.
Expand Down Expand Up @@ -569,6 +553,38 @@ def for_collection_constrained_field(self, field_definition: FieldDefinition) ->
# INFO: Removed because it was only for pydantic constrained collections
return schema

def for_enum_field(
self,
field_definition: FieldDefinition,
) -> Schema | Reference:
"""Create a schema instance for an enum.
Args:
field_definition: A signature field instance.
Returns:
A schema or reference instance.
"""
enum_type: None | OpenAPIType | list[OpenAPIType] = None
if issubclass(field_definition.annotation, Enum): # pragma: no branch
# This method is only called for enums, so this branch is always executed
if issubclass(field_definition.annotation, str): # StrEnum
enum_type = OpenAPIType.STRING
elif issubclass(field_definition.annotation, int): # IntEnum
enum_type = OpenAPIType.INTEGER

enum_values: list[Any] = [v.value for v in field_definition.annotation]
if enum_type is None:
enum_type = _types_in_list(enum_values)

schema = self.schema_registry.get_schema_for_field_definition(field_definition)
schema.type = enum_type
schema.enum = enum_values
schema.title = get_name(field_definition.annotation)
schema.description = field_definition.annotation.__doc__

return self.schema_registry.get_reference_for_field_definition(field_definition) or schema

def process_schema_result(self, field: FieldDefinition, schema: Schema) -> Schema | Reference:
if field.kwarg_definition and field.is_const and field.has_default and schema.const is None:
schema.const = field.default
Expand Down
Loading

0 comments on commit 1e15a22

Please sign in to comment.