From 4fec64fd57bc56957cdff2cccc108ae2a614b5a8 Mon Sep 17 00:00:00 2001 From: Federico Busetti <729029+febus982@users.noreply.github.com> Date: Mon, 3 Aug 2026 22:27:30 +0100 Subject: [PATCH] Modernise typing syntax and enforce pyupgrade Now that the floor is 3.11, PEP 604 unions and PEP 585 builtin generics are available everywhere, and the abstract collection types belong in collections.abc rather than typing. Enable ruff's UP (pyupgrade) ruleset so this is enforced rather than a one-off cleanup. Without it the codebase drifts back, because new code follows the style of the code around it. The ruleset also tracks target-version, so it will flag the next batch automatically whenever the floor moves again. Almost all of the diff is `ruff check --fix`: Union[X, Y] becomes X | Y, List/Dict/Type/Tuple become their builtin equivalents, and Mapping, Iterator and AsyncIterator move to collections.abc. Multi-line unions collapse to a single line, which is where most of the net reduction comes from. Three changes were made by hand: - PRIMARY_KEY is a module-level alias rather than an annotation, so rewriting it produces a types.UnionType at runtime instead of a typing.Union. Ruff will not apply that fix automatically. It is safe here because the alias lives in a private module and is not re-exported, so nothing downstream can observe the change. - _unit_of_work/__init__.py matches the "__init__.py" = ["F401"] per-file ignore, so the imports left unused by the rewrite had to be removed manually. - Sphinx :type: fields in docstrings still named the old spellings, which mkdocstrings publishes. They now match the annotations they document. No behaviour change. Full tox matrix green: py311 through py314, typing, lint and format, with coverage still at 100%. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01BJb27fB7d7HbQqfXc7U66V --- pyproject.toml | 1 + sqlalchemy_bind_manager/_bind_manager.py | 22 ++++----- .../_repository/abstract.py | 46 +++++++------------ sqlalchemy_bind_manager/_repository/async_.py | 36 ++++++--------- .../_repository/base_repository.py | 32 +++++-------- sqlalchemy_bind_manager/_repository/common.py | 22 ++++----- .../_repository/result_presenters.py | 13 +++--- sqlalchemy_bind_manager/_repository/sync.py | 36 ++++++--------- sqlalchemy_bind_manager/_session_handler.py | 2 +- .../_unit_of_work/__init__.py | 9 ++-- tests/conftest.py | 10 ++-- tests/repository/test_composite_pk.py | 6 +-- 12 files changed, 96 insertions(+), 139 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 621f889..885ce31 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -124,6 +124,7 @@ select = [ "I", # isort "N", # pep8-naming "S", # flake8-bandit + "UP", # pyupgrade "RUF", # ruff-specific-rules ] # Ignoring rules problematic with formatter diff --git a/sqlalchemy_bind_manager/_bind_manager.py b/sqlalchemy_bind_manager/_bind_manager.py index 50cef97..9124a2a 100644 --- a/sqlalchemy_bind_manager/_bind_manager.py +++ b/sqlalchemy_bind_manager/_bind_manager.py @@ -20,7 +20,8 @@ import atexit import weakref -from typing import ClassVar, Mapping, MutableMapping, Union +from collections.abc import Mapping, MutableMapping +from typing import ClassVar from pydantic import BaseModel, ConfigDict from sqlalchemy import MetaData, create_engine @@ -46,8 +47,8 @@ class SQLAlchemyConfig(BaseModel): """ engine_url: str - engine_options: Union[dict, None] = None - session_options: Union[dict, None] = None + engine_options: dict | None = None + session_options: dict | None = None async_engine: bool = False @@ -73,15 +74,12 @@ class SQLAlchemyAsyncBind(BaseModel): class SQLAlchemyBindManager: - __binds: MutableMapping[str, Union[SQLAlchemyBind, SQLAlchemyAsyncBind]] + __binds: MutableMapping[str, SQLAlchemyBind | SQLAlchemyAsyncBind] _instances: ClassVar[weakref.WeakSet["SQLAlchemyBindManager"]] = weakref.WeakSet() def __init__( self, - config: Union[ - Mapping[str, SQLAlchemyConfig], - SQLAlchemyConfig, - ], + config: Mapping[str, SQLAlchemyConfig] | SQLAlchemyConfig, ) -> None: self.__binds = {} if isinstance(config, Mapping): @@ -181,7 +179,7 @@ def get_bind_mappers_metadata(self) -> Mapping[str, MetaData]: def get_bind( self, bind_name: str = DEFAULT_BIND_NAME - ) -> Union[SQLAlchemyBind, SQLAlchemyAsyncBind]: + ) -> SQLAlchemyBind | SQLAlchemyAsyncBind: """ Returns a bind object by name. @@ -193,7 +191,7 @@ def get_bind( except KeyError: raise NotInitializedBindError("Bind not initialized") - def get_binds(self) -> Mapping[str, Union[SQLAlchemyBind, SQLAlchemyAsyncBind]]: + def get_binds(self) -> Mapping[str, SQLAlchemyBind | SQLAlchemyAsyncBind]: """ Returns all the registered bind objects. @@ -210,9 +208,7 @@ def get_mapper(self, bind_name: str = DEFAULT_BIND_NAME) -> registry: """ return self.get_bind(bind_name).registry_mapper - def get_session( - self, bind_name: str = DEFAULT_BIND_NAME - ) -> Union[Session, AsyncSession]: + def get_session(self, bind_name: str = DEFAULT_BIND_NAME) -> Session | AsyncSession: """ Returns a SQLAlchemy Session object, ready to be used either directly or as a context manager diff --git a/sqlalchemy_bind_manager/_repository/abstract.py b/sqlalchemy_bind_manager/_repository/abstract.py index d4dba3a..e44affb 100644 --- a/sqlalchemy_bind_manager/_repository/abstract.py +++ b/sqlalchemy_bind_manager/_repository/abstract.py @@ -18,15 +18,11 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. +from collections.abc import Iterable, Mapping from typing import ( Any, - Iterable, - List, Literal, - Mapping, Protocol, - Tuple, - Union, ) from .common import ( @@ -48,7 +44,7 @@ async def get(self, identifier: PRIMARY_KEY) -> MODEL: """ ... - async def get_many(self, identifiers: Iterable[PRIMARY_KEY]) -> List[MODEL]: + async def get_many(self, identifiers: Iterable[PRIMARY_KEY]) -> list[MODEL]: """Get a list of models by primary keys. :param identifiers: A list of primary keys @@ -88,11 +84,9 @@ async def delete_many(self, instances: Iterable[MODEL]) -> None: async def find( self, - search_params: Union[Mapping[str, Any], None] = None, - order_by: Union[ - Iterable[Union[str, Tuple[str, Literal["asc", "desc"]]]], None - ] = None, - ) -> List[MODEL]: + search_params: Mapping[str, Any] | None = None, + order_by: Iterable[str | tuple[str, Literal["asc", "desc"]]] | None = None, + ) -> list[MODEL]: """Find models using filters. E.g. @@ -116,10 +110,8 @@ async def paginated_find( self, items_per_page: int, page: int = 1, - search_params: Union[Mapping[str, Any], None] = None, - order_by: Union[ - Iterable[Union[str, Tuple[str, Literal["asc", "desc"]]]], None - ] = None, + search_params: Mapping[str, Any] | None = None, + order_by: Iterable[str | tuple[str, Literal["asc", "desc"]]] | None = None, ) -> PaginatedResult[MODEL]: """Find models using filters and limit/offset pagination. Returned results do include pagination metadata. @@ -152,9 +144,9 @@ async def paginated_find( async def cursor_paginated_find( self, items_per_page: int, - cursor_reference: Union[CursorReference, None] = None, + cursor_reference: CursorReference | None = None, is_before_cursor: bool = False, - search_params: Union[Mapping[str, Any], None] = None, + search_params: Mapping[str, Any] | None = None, ) -> CursorPaginatedResult[MODEL]: """Find models using filters and cursor based pagination. Returned results do include pagination metadata. @@ -194,7 +186,7 @@ def get(self, identifier: PRIMARY_KEY) -> MODEL: """ ... - def get_many(self, identifiers: Iterable[PRIMARY_KEY]) -> List[MODEL]: + def get_many(self, identifiers: Iterable[PRIMARY_KEY]) -> list[MODEL]: """Get a list of models by primary keys. :param identifiers: A list of primary keys @@ -234,11 +226,9 @@ def delete_many(self, instances: Iterable[MODEL]) -> None: def find( self, - search_params: Union[Mapping[str, Any], None] = None, - order_by: Union[ - Iterable[Union[str, Tuple[str, Literal["asc", "desc"]]]], None - ] = None, - ) -> List[MODEL]: + search_params: Mapping[str, Any] | None = None, + order_by: Iterable[str | tuple[str, Literal["asc", "desc"]]] | None = None, + ) -> list[MODEL]: """Find models using filters. E.g. @@ -262,10 +252,8 @@ def paginated_find( self, items_per_page: int, page: int = 1, - search_params: Union[Mapping[str, Any], None] = None, - order_by: Union[ - Iterable[Union[str, Tuple[str, Literal["asc", "desc"]]]], None - ] = None, + search_params: Mapping[str, Any] | None = None, + order_by: Iterable[str | tuple[str, Literal["asc", "desc"]]] | None = None, ) -> PaginatedResult[MODEL]: """Find models using filters and limit/offset pagination. Returned results do include pagination metadata. @@ -298,9 +286,9 @@ def paginated_find( def cursor_paginated_find( self, items_per_page: int, - cursor_reference: Union[CursorReference, None] = None, + cursor_reference: CursorReference | None = None, is_before_cursor: bool = False, - search_params: Union[Mapping[str, Any], None] = None, + search_params: Mapping[str, Any] | None = None, ) -> CursorPaginatedResult[MODEL]: """Find models using filters and cursor based pagination. Returned results do include pagination metadata. diff --git a/sqlalchemy_bind_manager/_repository/async_.py b/sqlalchemy_bind_manager/_repository/async_.py index 243b6c5..fef131f 100644 --- a/sqlalchemy_bind_manager/_repository/async_.py +++ b/sqlalchemy_bind_manager/_repository/async_.py @@ -18,18 +18,12 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. +from collections.abc import AsyncIterator, Iterable, Mapping from contextlib import asynccontextmanager from typing import ( Any, - AsyncIterator, Generic, - Iterable, - List, Literal, - Mapping, - Tuple, - Type, - Union, ) from sqlalchemy import select @@ -56,13 +50,13 @@ class SQLAlchemyAsyncRepository( BaseRepository[MODEL], ): _session_handler: AsyncSessionHandler - _external_session: Union[AsyncSession, None] + _external_session: AsyncSession | None def __init__( self, - bind: Union[SQLAlchemyAsyncBind, None] = None, - session: Union[AsyncSession, None] = None, - model_class: Union[Type[MODEL], None] = None, + bind: SQLAlchemyAsyncBind | None = None, + session: AsyncSession | None = None, + model_class: type[MODEL] | None = None, ) -> None: super().__init__(model_class=model_class) if not (bool(bind) ^ bool(session)): @@ -86,7 +80,7 @@ async def get(self, identifier: PRIMARY_KEY) -> MODEL: raise ModelNotFoundError("No rows found for provided primary key.") return model - async def get_many(self, identifiers: Iterable[PRIMARY_KEY]) -> List[MODEL]: + async def get_many(self, identifiers: Iterable[PRIMARY_KEY]) -> list[MODEL]: """Get a list of models by primary keys. :param identifiers: A list of primary keys @@ -145,11 +139,9 @@ async def delete_many(self, instances: Iterable[MODEL]) -> None: async def find( self, - search_params: Union[Mapping[str, Any], None] = None, - order_by: Union[ - Iterable[Union[str, Tuple[str, Literal["asc", "desc"]]]], None - ] = None, - ) -> List[MODEL]: + search_params: Mapping[str, Any] | None = None, + order_by: Iterable[str | tuple[str, Literal["asc", "desc"]]] | None = None, + ) -> list[MODEL]: """Find models using filters. E.g. @@ -177,10 +169,8 @@ async def paginated_find( self, items_per_page: int, page: int = 1, - search_params: Union[Mapping[str, Any], None] = None, - order_by: Union[ - Iterable[Union[str, Tuple[str, Literal["asc", "desc"]]]], None - ] = None, + search_params: Mapping[str, Any] | None = None, + order_by: Iterable[str | tuple[str, Literal["asc", "desc"]]] | None = None, ) -> PaginatedResult[MODEL]: """Find models using filters and limit/offset pagination. Returned results do include pagination metadata. @@ -229,9 +219,9 @@ async def paginated_find( async def cursor_paginated_find( self, items_per_page: int, - cursor_reference: Union[CursorReference, None] = None, + cursor_reference: CursorReference | None = None, is_before_cursor: bool = False, - search_params: Union[Mapping[str, Any], None] = None, + search_params: Mapping[str, Any] | None = None, ) -> CursorPaginatedResult[MODEL]: """Find models using filters and cursor based pagination. Returned results do include pagination metadata. diff --git a/sqlalchemy_bind_manager/_repository/base_repository.py b/sqlalchemy_bind_manager/_repository/base_repository.py index 0ce6dd9..abe2383 100644 --- a/sqlalchemy_bind_manager/_repository/base_repository.py +++ b/sqlalchemy_bind_manager/_repository/base_repository.py @@ -19,17 +19,11 @@ # DEALINGS IN THE SOFTWARE. from abc import ABC +from collections.abc import Callable, Iterable, Mapping from typing import ( Any, - Callable, - Dict, Generic, - Iterable, Literal, - Mapping, - Tuple, - Type, - Union, ) from sqlalchemy import asc, desc, func, select @@ -48,9 +42,9 @@ class BaseRepository(Generic[MODEL], ABC): _max_query_limit: int = 50 - _model: Type[MODEL] + _model: type[MODEL] - def __init__(self, model_class: Union[Type[MODEL], None] = None) -> None: + def __init__(self, model_class: type[MODEL] | None = None) -> None: if getattr(self, "_model", None) is None and model_class is not None: self._model = model_class @@ -63,7 +57,7 @@ def __init__(self, model_class: Union[Type[MODEL], None] = None) -> None: " or in the `_model` class property." ) - def _is_mapped_class(self, class_: Type[MODEL]) -> bool: + def _is_mapped_class(self, class_: type[MODEL]) -> bool: """Checks if the class is mapped in SQLAlchemy. :param class_: the model class @@ -116,7 +110,7 @@ def _filter_select(self, stmt: Select, search_params: Mapping[str, Any]) -> Sele def _filter_order_by( self, stmt: Select, - order_by: Iterable[Union[str, Tuple[str, Literal["asc", "desc"]]]], + order_by: Iterable[str | tuple[str, Literal["asc", "desc"]]], ) -> Select: """Build the query ordering clauses from submitted parameters. @@ -131,7 +125,7 @@ def _filter_order_by( :param order_by: a list of columns, or tuples (column, direction) :return: The filtered query """ - _order_funcs: Dict[Literal["asc", "desc"], Callable] = { + _order_funcs: dict[Literal["asc", "desc"], Callable] = { "desc": desc, "asc": asc, } @@ -150,10 +144,8 @@ def _filter_order_by( def _find_query( self, - search_params: Union[Mapping[str, Any], None] = None, - order_by: Union[ - Iterable[Union[str, Tuple[str, Literal["asc", "desc"]]]], None - ] = None, + search_params: Mapping[str, Any] | None = None, + order_by: Iterable[str | tuple[str, Literal["asc", "desc"]]] | None = None, ) -> Select: """Build a query with column filters and orders. @@ -217,7 +209,7 @@ def _paginate_query_by_page( def _cursor_paginated_query( self, stmt: Select, - cursor_reference: Union[CursorReference, None], + cursor_reference: CursorReference | None, is_before_cursor: bool = False, items_per_page: int = _max_query_limit, ) -> Select: @@ -229,7 +221,7 @@ def _cursor_paginated_query( :type stmt: Select :param cursor_reference: A cursor reference containing ordering column and threshold value - :type cursor_reference: Union[CursorReference, None] + :type cursor_reference: CursorReference | None :param is_before_cursor: If True it will return items before the cursor, otherwise items after :type is_before_cursor: bool @@ -277,7 +269,7 @@ def _cursor_pagination_slice_query( :type stmt: Select :param cursor_reference: A cursor reference containing ordering column and threshold value - :type cursor_reference: Union[CursorReference, None] + :type cursor_reference: CursorReference | None :param is_before_cursor: If True it will return items before the cursor, otherwise items after :type is_before_cursor: bool @@ -311,7 +303,7 @@ def _cursor_pagination_previous_item_query( :type stmt: Select :param cursor_reference: A cursor reference containing ordering column and threshold value - :type cursor_reference: Union[CursorReference, None] + :type cursor_reference: CursorReference | None :param is_before_cursor: If True it will return items before the cursor, otherwise items after :type is_before_cursor: bool diff --git a/sqlalchemy_bind_manager/_repository/common.py b/sqlalchemy_bind_manager/_repository/common.py index 8a2abef..9008276 100644 --- a/sqlalchemy_bind_manager/_repository/common.py +++ b/sqlalchemy_bind_manager/_repository/common.py @@ -18,14 +18,14 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -from typing import Generic, List, Type, TypeVar, Union +from typing import Generic, TypeVar from uuid import UUID from pydantic import BaseModel, StrictInt, StrictStr from sqlalchemy import inspect MODEL = TypeVar("MODEL") -PRIMARY_KEY = Union[str, int, tuple, dict, UUID] +PRIMARY_KEY = str | int | tuple | dict | UUID # Constrained rather than bound: a bound TypeVar would happily bind to the # union of the constraints, which lets mismatched operands (e.g. `str >= UUID`) @@ -35,7 +35,7 @@ CURSOR_VALUE = TypeVar("CURSOR_VALUE", StrictStr, StrictInt, UUID) -def get_model_pk_name(model_class: Type) -> str: +def get_model_pk_name(model_class: type) -> str: """Retrieves the primary key column name from a SQLAlchemy model class. :param model_class: A SQLAlchemy model class @@ -79,12 +79,12 @@ class PaginatedResult(BaseModel, Generic[MODEL]): The result of a paginated query. :param items: The models returned by the query - :type items: List[MODEL] + :type items: list[MODEL] :param page_info: The pagination metadata :type page_info: PageInfo """ - items: List[MODEL] + items: list[MODEL] page_info: PageInfo @@ -116,18 +116,18 @@ class CursorPageInfo(BaseModel): :type has_previous_page: bool :param start_cursor: The cursor pointing to the first item in the page, if at least one item is returned. - :type start_cursor: Union[CursorReference, None] + :type start_cursor: CursorReference | None :param end_cursor: The cursor pointing to the last item in the page, if at least one item is returned. - :type end_cursor: Union[CursorReference, None] + :type end_cursor: CursorReference | None """ items_per_page: int total_items: int has_next_page: bool = False has_previous_page: bool = False - start_cursor: Union[CursorReference, None] = None - end_cursor: Union[CursorReference, None] = None + start_cursor: CursorReference | None = None + end_cursor: CursorReference | None = None class CursorPaginatedResult(BaseModel, Generic[MODEL]): @@ -135,10 +135,10 @@ class CursorPaginatedResult(BaseModel, Generic[MODEL]): The result of a cursor paginated query. :param items: The models returned by the query - :type items: List[MODEL] + :type items: list[MODEL] :param page_info: The pagination metadata :type page_info: CursorPageInfo """ - items: List[MODEL] + items: list[MODEL] page_info: CursorPageInfo diff --git a/sqlalchemy_bind_manager/_repository/result_presenters.py b/sqlalchemy_bind_manager/_repository/result_presenters.py index dee9e47..d457982 100644 --- a/sqlalchemy_bind_manager/_repository/result_presenters.py +++ b/sqlalchemy_bind_manager/_repository/result_presenters.py @@ -19,7 +19,6 @@ # DEALINGS IN THE SOFTWARE. from math import ceil -from typing import List, Union from .common import ( CURSOR_VALUE, @@ -37,10 +36,10 @@ class CursorPaginatedResultPresenter: @classmethod def build_result( cls, - result_items: List[MODEL], + result_items: list[MODEL], total_items_count: int, items_per_page: int, - cursor_reference: Union[CursorReference, None], + cursor_reference: CursorReference | None, is_before_cursor: bool, ) -> CursorPaginatedResult: """ @@ -86,7 +85,7 @@ def _build_empty_items_result( @staticmethod def _build_no_cursor_result( - result_items: List[MODEL], + result_items: list[MODEL], total_items_count: int, items_per_page: int, ) -> CursorPaginatedResult: @@ -115,7 +114,7 @@ def _build_no_cursor_result( @staticmethod def _build_before_cursor_result( - result_items: List[MODEL], + result_items: list[MODEL], total_items_count: int, items_per_page: int, cursor_reference: CursorReference[CURSOR_VALUE], @@ -164,7 +163,7 @@ def _build_before_cursor_result( @staticmethod def _build_after_cursor_result( - result_items: List[MODEL], + result_items: list[MODEL], total_items_count: int, items_per_page: int, cursor_reference: CursorReference[CURSOR_VALUE], @@ -215,7 +214,7 @@ def _build_after_cursor_result( class PaginatedResultPresenter: @staticmethod def build_result( - result_items: List[MODEL], + result_items: list[MODEL], total_items_count: int, page: int, items_per_page: int, diff --git a/sqlalchemy_bind_manager/_repository/sync.py b/sqlalchemy_bind_manager/_repository/sync.py index 0cc3398..4e6d6d8 100644 --- a/sqlalchemy_bind_manager/_repository/sync.py +++ b/sqlalchemy_bind_manager/_repository/sync.py @@ -18,18 +18,12 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. +from collections.abc import Iterable, Iterator, Mapping from contextlib import contextmanager from typing import ( Any, Generic, - Iterable, - Iterator, - List, Literal, - Mapping, - Tuple, - Type, - Union, ) from sqlalchemy import select @@ -56,13 +50,13 @@ class SQLAlchemyRepository( BaseRepository[MODEL], ): _session_handler: SessionHandler - _external_session: Union[Session, None] + _external_session: Session | None def __init__( self, - bind: Union[SQLAlchemyBind, None] = None, - session: Union[Session, None] = None, - model_class: Union[Type[MODEL], None] = None, + bind: SQLAlchemyBind | None = None, + session: Session | None = None, + model_class: type[MODEL] | None = None, ) -> None: super().__init__(model_class=model_class) if not (bool(bind) ^ bool(session)): @@ -86,7 +80,7 @@ def get(self, identifier: PRIMARY_KEY) -> MODEL: raise ModelNotFoundError("No rows found for provided primary key.") return model - def get_many(self, identifiers: Iterable[PRIMARY_KEY]) -> List[MODEL]: + def get_many(self, identifiers: Iterable[PRIMARY_KEY]) -> list[MODEL]: """Get a list of models by primary keys. :param identifiers: A list of primary keys @@ -142,11 +136,9 @@ def delete_many(self, instances: Iterable[MODEL]) -> None: def find( self, - search_params: Union[Mapping[str, Any], None] = None, - order_by: Union[ - Iterable[Union[str, Tuple[str, Literal["asc", "desc"]]]], None - ] = None, - ) -> List[MODEL]: + search_params: Mapping[str, Any] | None = None, + order_by: Iterable[str | tuple[str, Literal["asc", "desc"]]] | None = None, + ) -> list[MODEL]: """Find models using filters. E.g. @@ -174,10 +166,8 @@ def paginated_find( self, items_per_page: int, page: int = 1, - search_params: Union[Mapping[str, Any], None] = None, - order_by: Union[ - Iterable[Union[str, Tuple[str, Literal["asc", "desc"]]]], None - ] = None, + search_params: Mapping[str, Any] | None = None, + order_by: Iterable[str | tuple[str, Literal["asc", "desc"]]] | None = None, ) -> PaginatedResult[MODEL]: """Find models using filters and limit/offset pagination. Returned results do include pagination metadata. @@ -224,9 +214,9 @@ def paginated_find( def cursor_paginated_find( self, items_per_page: int, - cursor_reference: Union[CursorReference, None] = None, + cursor_reference: CursorReference | None = None, is_before_cursor: bool = False, - search_params: Union[Mapping[str, Any], None] = None, + search_params: Mapping[str, Any] | None = None, ) -> CursorPaginatedResult[MODEL]: """Find models using filters and cursor based pagination. Returned results do include pagination metadata. diff --git a/sqlalchemy_bind_manager/_session_handler.py b/sqlalchemy_bind_manager/_session_handler.py index 35989b5..dea25bc 100644 --- a/sqlalchemy_bind_manager/_session_handler.py +++ b/sqlalchemy_bind_manager/_session_handler.py @@ -20,8 +20,8 @@ import asyncio import logging +from collections.abc import AsyncIterator, Iterator from contextlib import asynccontextmanager, contextmanager -from typing import AsyncIterator, Iterator from sqlalchemy.ext.asyncio import ( AsyncSession, diff --git a/sqlalchemy_bind_manager/_unit_of_work/__init__.py b/sqlalchemy_bind_manager/_unit_of_work/__init__.py index defadf2..b4b2e6e 100644 --- a/sqlalchemy_bind_manager/_unit_of_work/__init__.py +++ b/sqlalchemy_bind_manager/_unit_of_work/__init__.py @@ -19,8 +19,9 @@ # DEALINGS IN THE SOFTWARE. from abc import ABC +from collections.abc import AsyncIterator, Iterator from contextlib import asynccontextmanager, contextmanager -from typing import AsyncIterator, Dict, Generic, Iterator, Type, TypeVar, Union +from typing import Generic, TypeVar from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import Session @@ -42,7 +43,7 @@ class BaseUnitOfWork(Generic[REPOSITORY, SESSION_HANDLER], ABC): _session_handler: SESSION_HANDLER - _repositories: Dict[str, REPOSITORY] + _repositories: dict[str, REPOSITORY] def __init__(self): self._repositories = {} @@ -50,8 +51,8 @@ def __init__(self): def register_repository( self, name: str, - repository_class: Type[REPOSITORY], - model_class: Union[Type, None] = None, + repository_class: type[REPOSITORY], + model_class: type | None = None, *args, **kwargs, ): diff --git a/tests/conftest.py b/tests/conftest.py index f84e1b1..80f2373 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,6 @@ import inspect from contextlib import _AsyncGeneratorContextManager, asynccontextmanager -from typing import ClassVar, Tuple, Type, Union +from typing import ClassVar from uuid import uuid4 import pytest @@ -106,7 +106,7 @@ def sa_bind(request, sa_manager): @pytest.fixture -async def model_classes(sa_bind) -> Tuple[Type, Type]: +async def model_classes(sa_bind) -> tuple[type, type]: class ParentModel(sa_bind.declarative_base): __tablename__ = "parent_model" # required in order to access columns with server defaults @@ -149,7 +149,7 @@ class ChildModel(sa_bind.declarative_base): @pytest.fixture -async def model_class(model_classes: Tuple[Type, Type]) -> Type: +async def model_class(model_classes: tuple[type, type]) -> type: return model_classes[0] @@ -164,8 +164,8 @@ def session_handler_class(sa_bind): @pytest.fixture def repository_class( - sa_bind: Union[SQLAlchemyBind, SQLAlchemyAsyncBind], -) -> Type[Union[SQLAlchemyAsyncRepository, SQLAlchemyRepository]]: + sa_bind: SQLAlchemyBind | SQLAlchemyAsyncBind, +) -> type[SQLAlchemyAsyncRepository | SQLAlchemyRepository]: base_class = ( SQLAlchemyRepository if isinstance(sa_bind, SQLAlchemyBind) diff --git a/tests/repository/test_composite_pk.py b/tests/repository/test_composite_pk.py index ca87d1e..ccf2d3f 100644 --- a/tests/repository/test_composite_pk.py +++ b/tests/repository/test_composite_pk.py @@ -1,4 +1,4 @@ -from typing import ClassVar, Type +from typing import ClassVar import pytest from sqlalchemy import Column, Integer, String @@ -23,7 +23,7 @@ def sa_manager() -> SQLAlchemyBindManager: @pytest.fixture -def model_class_composite_pk(sa_manager) -> Type: +def model_class_composite_pk(sa_manager) -> type: default_bind = sa_manager.get_bind() class MyModel(default_bind.declarative_base): @@ -43,7 +43,7 @@ class MyModel(default_bind.declarative_base): @pytest.fixture -def repository_class(model_class_composite_pk) -> Type[SQLAlchemyRepository]: +def repository_class(model_class_composite_pk) -> type[SQLAlchemyRepository]: class MyRepository(SQLAlchemyRepository[model_class_composite_pk]): _model = model_class_composite_pk