Skip to content

Commit 082e0fc

Browse files
authored
Merge branch 'main' into renovate/astral-sh-setup-uv-8.x
2 parents 4bce43c + 67ad167 commit 082e0fc

13 files changed

Lines changed: 3888 additions & 50 deletions

File tree

‎.github/workflows/release.yml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
uv build
3434
3535
- name: Archive the dist folder
36-
uses: actions/upload-artifact@v6
36+
uses: actions/upload-artifact@v7
3737
with:
3838
name: dist
3939
path: dist
@@ -56,7 +56,7 @@ jobs:
5656

5757
steps:
5858
- name: Download the dist folder from the build job
59-
uses: actions/download-artifact@v7
59+
uses: actions/download-artifact@v8
6060
with:
6161
name: dist
6262
path: dist

‎.gitignore‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,5 +282,4 @@ dmypy.json
282282

283283
# Cython debug symbols
284284
cython_debug/
285-
uv.lock
286285
version.py

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ testpaths = [
115115
]
116116

117117
[tool.ruff]
118-
extend-exclude = ["docs", ".tox"]
118+
extend-exclude = ["docs", ".tox", "*.md"]
119119
target-version = "py39"
120120

121121
[tool.ruff.lint]

‎sqlalchemy_bind_manager/_bind_manager.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
import weakref
2323
from typing import ClassVar, Mapping, MutableMapping, Union
2424

25-
from pydantic import BaseModel, ConfigDict, StrictBool
25+
from pydantic import BaseModel, ConfigDict
2626
from sqlalchemy import MetaData, create_engine
2727
from sqlalchemy.engine import Engine
2828
from sqlalchemy.ext.asyncio import (
@@ -48,7 +48,7 @@ class SQLAlchemyConfig(BaseModel):
4848
engine_url: str
4949
engine_options: Union[dict, None] = None
5050
session_options: Union[dict, None] = None
51-
async_engine: StrictBool = False
51+
async_engine: bool = False
5252

5353

5454
class SQLAlchemyBind(BaseModel):

‎sqlalchemy_bind_manager/_repository/abstract.py‎

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,9 @@ async def delete_many(self, instances: Iterable[MODEL]) -> None:
8888

8989
async def find(
9090
self,
91-
search_params: Union[None, Mapping[str, Any]] = None,
91+
search_params: Union[Mapping[str, Any], None] = None,
9292
order_by: Union[
93-
None,
94-
Iterable[Union[str, Tuple[str, Literal["asc", "desc"]]]],
93+
Iterable[Union[str, Tuple[str, Literal["asc", "desc"]]]], None
9594
] = None,
9695
) -> List[MODEL]:
9796
"""Find models using filters.
@@ -117,10 +116,9 @@ async def paginated_find(
117116
self,
118117
items_per_page: int,
119118
page: int = 1,
120-
search_params: Union[None, Mapping[str, Any]] = None,
119+
search_params: Union[Mapping[str, Any], None] = None,
121120
order_by: Union[
122-
None,
123-
Iterable[Union[str, Tuple[str, Literal["asc", "desc"]]]],
121+
Iterable[Union[str, Tuple[str, Literal["asc", "desc"]]]], None
124122
] = None,
125123
) -> PaginatedResult[MODEL]:
126124
"""Find models using filters and limit/offset pagination. Returned results
@@ -156,7 +154,7 @@ async def cursor_paginated_find(
156154
items_per_page: int,
157155
cursor_reference: Union[CursorReference, None] = None,
158156
is_before_cursor: bool = False,
159-
search_params: Union[None, Mapping[str, Any]] = None,
157+
search_params: Union[Mapping[str, Any], None] = None,
160158
) -> CursorPaginatedResult[MODEL]:
161159
"""Find models using filters and cursor based pagination. Returned results
162160
do include pagination metadata.
@@ -236,10 +234,9 @@ def delete_many(self, instances: Iterable[MODEL]) -> None:
236234

237235
def find(
238236
self,
239-
search_params: Union[None, Mapping[str, Any]] = None,
237+
search_params: Union[Mapping[str, Any], None] = None,
240238
order_by: Union[
241-
None,
242-
Iterable[Union[str, Tuple[str, Literal["asc", "desc"]]]],
239+
Iterable[Union[str, Tuple[str, Literal["asc", "desc"]]]], None
243240
] = None,
244241
) -> List[MODEL]:
245242
"""Find models using filters.
@@ -265,10 +262,9 @@ def paginated_find(
265262
self,
266263
items_per_page: int,
267264
page: int = 1,
268-
search_params: Union[None, Mapping[str, Any]] = None,
265+
search_params: Union[Mapping[str, Any], None] = None,
269266
order_by: Union[
270-
None,
271-
Iterable[Union[str, Tuple[str, Literal["asc", "desc"]]]],
267+
Iterable[Union[str, Tuple[str, Literal["asc", "desc"]]]], None
272268
] = None,
273269
) -> PaginatedResult[MODEL]:
274270
"""Find models using filters and limit/offset pagination. Returned results
@@ -304,7 +300,7 @@ def cursor_paginated_find(
304300
items_per_page: int,
305301
cursor_reference: Union[CursorReference, None] = None,
306302
is_before_cursor: bool = False,
307-
search_params: Union[None, Mapping[str, Any]] = None,
303+
search_params: Union[Mapping[str, Any], None] = None,
308304
) -> CursorPaginatedResult[MODEL]:
309305
"""Find models using filters and cursor based pagination. Returned results
310306
do include pagination metadata.

‎sqlalchemy_bind_manager/_repository/async_.py‎

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,9 @@ async def delete_many(self, instances: Iterable[MODEL]) -> None:
145145

146146
async def find(
147147
self,
148-
search_params: Union[None, Mapping[str, Any]] = None,
148+
search_params: Union[Mapping[str, Any], None] = None,
149149
order_by: Union[
150-
None,
151-
Iterable[Union[str, Tuple[str, Literal["asc", "desc"]]]],
150+
Iterable[Union[str, Tuple[str, Literal["asc", "desc"]]]], None
152151
] = None,
153152
) -> List[MODEL]:
154153
"""Find models using filters.
@@ -170,18 +169,17 @@ async def find(
170169
"""
171170
stmt = self._find_query(search_params, order_by)
172171

173-
async with self._get_session() as session:
172+
async with self._get_session(commit=False) as session:
174173
result = await session.execute(stmt)
175174
return [x for x in result.scalars()]
176175

177176
async def paginated_find(
178177
self,
179178
items_per_page: int,
180179
page: int = 1,
181-
search_params: Union[None, Mapping[str, Any]] = None,
180+
search_params: Union[Mapping[str, Any], None] = None,
182181
order_by: Union[
183-
None,
184-
Iterable[Union[str, Tuple[str, Literal["asc", "desc"]]]],
182+
Iterable[Union[str, Tuple[str, Literal["asc", "desc"]]]], None
185183
] = None,
186184
) -> PaginatedResult[MODEL]:
187185
"""Find models using filters and limit/offset pagination. Returned results
@@ -213,7 +211,7 @@ async def paginated_find(
213211
find_stmt = self._find_query(search_params, order_by)
214212
paginated_stmt = self._paginate_query_by_page(find_stmt, page, items_per_page)
215213

216-
async with self._get_session() as session:
214+
async with self._get_session(commit=False) as session:
217215
total_items_count = (
218216
await session.execute(self._count_query(find_stmt))
219217
).scalar() or 0
@@ -233,7 +231,7 @@ async def cursor_paginated_find(
233231
items_per_page: int,
234232
cursor_reference: Union[CursorReference, None] = None,
235233
is_before_cursor: bool = False,
236-
search_params: Union[None, Mapping[str, Any]] = None,
234+
search_params: Union[Mapping[str, Any], None] = None,
237235
) -> CursorPaginatedResult[MODEL]:
238236
"""Find models using filters and cursor based pagination. Returned results
239237
do include pagination metadata.
@@ -268,7 +266,7 @@ async def cursor_paginated_find(
268266
items_per_page=items_per_page,
269267
)
270268

271-
async with self._get_session() as session:
269+
async with self._get_session(commit=False) as session:
272270
total_items_count = (
273271
await session.execute(self._count_query(find_stmt))
274272
).scalar() or 0

‎sqlalchemy_bind_manager/_repository/base_repository.py‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,10 +150,9 @@ def _filter_order_by(
150150

151151
def _find_query(
152152
self,
153-
search_params: Union[None, Mapping[str, Any]] = None,
153+
search_params: Union[Mapping[str, Any], None] = None,
154154
order_by: Union[
155-
None,
156-
Iterable[Union[str, Tuple[str, Literal["asc", "desc"]]]],
155+
Iterable[Union[str, Tuple[str, Literal["asc", "desc"]]]], None
157156
] = None,
158157
) -> Select:
159158
"""Build a query with column filters and orders.

‎sqlalchemy_bind_manager/_repository/common.py‎

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@
2727
MODEL = TypeVar("MODEL")
2828
PRIMARY_KEY = Union[str, int, tuple, dict, UUID]
2929

30+
# Constrained rather than bound: a bound TypeVar would happily bind to the
31+
# union of the constraints, which lets mismatched operands (e.g. `str >= UUID`)
32+
# pass unnoticed. Constraining makes the checker solve for one concrete type,
33+
# so a cursor value and the reference it is compared against are provably the
34+
# same type.
35+
CURSOR_VALUE = TypeVar("CURSOR_VALUE", StrictStr, StrictInt, UUID)
36+
3037

3138
def get_model_pk_name(model_class: Type) -> str:
3239
"""Retrieves the primary key column name from a SQLAlchemy model class.
@@ -81,9 +88,18 @@ class PaginatedResult(BaseModel, Generic[MODEL]):
8188
page_info: PageInfo
8289

8390

84-
class CursorReference(BaseModel):
91+
class CursorReference(BaseModel, Generic[CURSOR_VALUE]):
92+
"""A cursor position: an ordering column and a threshold value.
93+
94+
Generic in the value type so a cursor value read from a model and the
95+
reference it is compared against are known to be the same type. The
96+
parameter can be omitted (`CursorReference(column="id", value=123)`);
97+
it is inferred, and existing annotations that name the class bare keep
98+
working.
99+
"""
100+
85101
column: str
86-
value: Union[StrictStr, StrictInt, UUID]
102+
value: CURSOR_VALUE
87103

88104

89105
class CursorPageInfo(BaseModel):

‎sqlalchemy_bind_manager/_repository/result_presenters.py‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from typing import List, Union
2323

2424
from .common import (
25+
CURSOR_VALUE,
2526
MODEL,
2627
CursorPageInfo,
2728
CursorPaginatedResult,
@@ -117,11 +118,13 @@ def _build_before_cursor_result(
117118
result_items: List[MODEL],
118119
total_items_count: int,
119120
items_per_page: int,
120-
cursor_reference: CursorReference,
121+
cursor_reference: CursorReference[CURSOR_VALUE],
121122
) -> CursorPaginatedResult:
122123
index = -1
123124
reference_column = cursor_reference.column
124-
last_found_cursor_value = getattr(result_items[index], reference_column)
125+
last_found_cursor_value: CURSOR_VALUE = getattr(
126+
result_items[index], reference_column
127+
)
125128
if not isinstance(last_found_cursor_value, type(cursor_reference.value)):
126129
raise TypeError(
127130
"Values from CursorReference and results must be of the same type"
@@ -164,11 +167,13 @@ def _build_after_cursor_result(
164167
result_items: List[MODEL],
165168
total_items_count: int,
166169
items_per_page: int,
167-
cursor_reference: CursorReference,
170+
cursor_reference: CursorReference[CURSOR_VALUE],
168171
) -> CursorPaginatedResult:
169172
index = 0
170173
reference_column = cursor_reference.column
171-
first_found_cursor_value = getattr(result_items[index], reference_column)
174+
first_found_cursor_value: CURSOR_VALUE = getattr(
175+
result_items[index], reference_column
176+
)
172177
if not isinstance(first_found_cursor_value, type(cursor_reference.value)):
173178
raise TypeError(
174179
"Values from CursorReference and results must be of the same type"

‎sqlalchemy_bind_manager/_repository/sync.py‎

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,9 @@ def delete_many(self, instances: Iterable[MODEL]) -> None:
142142

143143
def find(
144144
self,
145-
search_params: Union[None, Mapping[str, Any]] = None,
145+
search_params: Union[Mapping[str, Any], None] = None,
146146
order_by: Union[
147-
None,
148-
Iterable[Union[str, Tuple[str, Literal["asc", "desc"]]]],
147+
Iterable[Union[str, Tuple[str, Literal["asc", "desc"]]]], None
149148
] = None,
150149
) -> List[MODEL]:
151150
"""Find models using filters.
@@ -167,18 +166,17 @@ def find(
167166
"""
168167
stmt = self._find_query(search_params, order_by)
169168

170-
with self._get_session() as session:
169+
with self._get_session(commit=False) as session:
171170
result = session.execute(stmt)
172171
return [x for x in result.scalars()]
173172

174173
def paginated_find(
175174
self,
176175
items_per_page: int,
177176
page: int = 1,
178-
search_params: Union[None, Mapping[str, Any]] = None,
177+
search_params: Union[Mapping[str, Any], None] = None,
179178
order_by: Union[
180-
None,
181-
Iterable[Union[str, Tuple[str, Literal["asc", "desc"]]]],
179+
Iterable[Union[str, Tuple[str, Literal["asc", "desc"]]]], None
182180
] = None,
183181
) -> PaginatedResult[MODEL]:
184182
"""Find models using filters and limit/offset pagination. Returned results
@@ -210,7 +208,7 @@ def paginated_find(
210208
find_stmt = self._find_query(search_params, order_by)
211209
paginated_stmt = self._paginate_query_by_page(find_stmt, page, items_per_page)
212210

213-
with self._get_session() as session:
211+
with self._get_session(commit=False) as session:
214212
total_items_count = (
215213
session.execute(self._count_query(find_stmt)).scalar() or 0
216214
)
@@ -228,7 +226,7 @@ def cursor_paginated_find(
228226
items_per_page: int,
229227
cursor_reference: Union[CursorReference, None] = None,
230228
is_before_cursor: bool = False,
231-
search_params: Union[None, Mapping[str, Any]] = None,
229+
search_params: Union[Mapping[str, Any], None] = None,
232230
) -> CursorPaginatedResult[MODEL]:
233231
"""Find models using filters and cursor based pagination. Returned results
234232
do include pagination metadata.
@@ -264,7 +262,7 @@ def cursor_paginated_find(
264262
items_per_page=items_per_page,
265263
)
266264

267-
with self._get_session() as session:
265+
with self._get_session(commit=False) as session:
268266
total_items_count = (
269267
session.execute(self._count_query(find_stmt)).scalar() or 0
270268
)

0 commit comments

Comments
 (0)