Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/python-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
strategy:
fail-fast: false
matrix:
version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
version: ["3.11", "3.12", "3.13", "3.14"]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# SQLAlchemy bind manager
![Static Badge](https://img.shields.io/badge/Python-3.9_%7C_3.10_%7C_3.11_%7C_3.12_%7C_3.13_%7C_3.14-blue?logo=python&logoColor=white)
![Static Badge](https://img.shields.io/badge/Python-3.11_%7C_3.12_%7C_3.13_%7C_3.14-blue?logo=python&logoColor=white)
[![Stable Version](https://img.shields.io/pypi/v/sqlalchemy-bind-manager?color=blue)](https://pypi.org/project/sqlalchemy-bind-manager/)
[![stability-beta](https://img.shields.io/badge/stability-beta-33bbff.svg)](https://github.com/mkenney/software-guides/blob/master/STABILITY-BADGES.md#beta)

Expand Down
9 changes: 4 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "sqlalchemy-bind-manager"
dynamic = ["version"]
description = "A manager to easily handle multiple SQLAlchemy configurations"
authors = [{ name = "Federico Busetti", email = "729029+febus982@users.noreply.github.com" }]
requires-python = ">=3.9,<3.15"
requires-python = ">=3.11,<3.15"
readme = "README.md"
license = "MIT"
keywords = [
Expand All @@ -22,8 +22,6 @@ classifiers = [
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
Expand Down Expand Up @@ -102,7 +100,7 @@ omit = [

[tool.mypy]
files = "sqlalchemy_bind_manager"
python_version = "3.9"
python_version = "3.11"
plugins = "pydantic.mypy"

[tool.pytest.ini_options]
Expand All @@ -116,7 +114,7 @@ testpaths = [

[tool.ruff]
extend-exclude = ["docs", ".tox", "*.md"]
target-version = "py39"
target-version = "py311"

[tool.ruff.lint]
select = [
Expand All @@ -126,6 +124,7 @@ select = [
"I", # isort
"N", # pep8-naming
"S", # flake8-bandit
"UP", # pyupgrade
"RUF", # ruff-specific-rules
]
# Ignoring rules problematic with formatter
Expand Down
22 changes: 9 additions & 13 deletions sqlalchemy_bind_manager/_bind_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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


Expand All @@ -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):
Expand Down Expand Up @@ -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.

Expand All @@ -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.

Expand All @@ -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
Expand Down
46 changes: 17 additions & 29 deletions sqlalchemy_bind_manager/_repository/abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
36 changes: 13 additions & 23 deletions sqlalchemy_bind_manager/_repository/async_.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)):
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
Loading
Loading