fastapi-toolsets 2.4.1__tar.gz → 2.4.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/PKG-INFO +1 -1
- {fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/pyproject.toml +3 -2
- {fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/__init__.py +1 -1
- {fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/crud/factory.py +1 -1
- {fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/db.py +1 -0
- {fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/exceptions/handler.py +1 -1
- {fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/logger.py +1 -1
- {fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/models/watched.py +40 -2
- {fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/pytest/utils.py +2 -1
- {fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/schemas.py +3 -3
- {fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/LICENSE +0 -0
- {fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/README.md +0 -0
- {fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/_imports.py +0 -0
- {fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/cli/__init__.py +0 -0
- {fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/cli/app.py +0 -0
- {fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/cli/commands/__init__.py +0 -0
- {fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/cli/commands/fixtures.py +0 -0
- {fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/cli/config.py +0 -0
- {fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/cli/pyproject.py +0 -0
- {fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/cli/utils.py +0 -0
- {fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/crud/__init__.py +0 -0
- {fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/crud/search.py +0 -0
- {fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/dependencies.py +0 -0
- {fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/exceptions/__init__.py +0 -0
- {fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/exceptions/exceptions.py +0 -0
- {fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/fixtures/__init__.py +0 -0
- {fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/fixtures/enum.py +0 -0
- {fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/fixtures/registry.py +0 -0
- {fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/fixtures/utils.py +0 -0
- {fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/metrics/__init__.py +0 -0
- {fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/metrics/handler.py +0 -0
- {fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/metrics/registry.py +0 -0
- {fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/models/__init__.py +0 -0
- {fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/models/columns.py +0 -0
- {fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/py.typed +0 -0
- {fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/pytest/__init__.py +0 -0
- {fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/pytest/plugin.py +0 -0
- {fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/types.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "fastapi-toolsets"
|
|
3
|
-
version = "2.4.
|
|
3
|
+
version = "2.4.3"
|
|
4
4
|
description = "Production-ready utilities for FastAPI applications"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -67,6 +67,7 @@ dev = [
|
|
|
67
67
|
{include-group = "tests"},
|
|
68
68
|
{include-group = "docs"},
|
|
69
69
|
"fastapi-toolsets[all]",
|
|
70
|
+
"prek>=0.3.8",
|
|
70
71
|
"ruff>=0.1.0",
|
|
71
72
|
"ty>=0.0.1a0",
|
|
72
73
|
]
|
|
@@ -84,7 +85,7 @@ docs = [
|
|
|
84
85
|
]
|
|
85
86
|
|
|
86
87
|
[build-system]
|
|
87
|
-
requires = ["uv_build>=0.10,<0.
|
|
88
|
+
requires = ["uv_build>=0.10,<0.12.0"]
|
|
88
89
|
build-backend = "uv_build"
|
|
89
90
|
|
|
90
91
|
[tool.pytest.ini_options]
|
|
@@ -295,7 +295,7 @@ class AsyncCrud(Generic[ModelType]):
|
|
|
295
295
|
return {k: v for k, v in kwargs.items() if v is not None}
|
|
296
296
|
|
|
297
297
|
dependency.__name__ = f"{cls.model.__name__}FilterParams"
|
|
298
|
-
dependency.__signature__ = inspect.Signature( # type: ignore[attr-defined]
|
|
298
|
+
dependency.__signature__ = inspect.Signature( # type: ignore[attr-defined] # ty:ignore[unresolved-attribute]
|
|
299
299
|
parameters=[
|
|
300
300
|
inspect.Parameter(
|
|
301
301
|
k,
|
{fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/exceptions/handler.py
RENAMED
|
@@ -30,7 +30,7 @@ def init_exceptions_handlers(app: FastAPI) -> FastAPI:
|
|
|
30
30
|
"""
|
|
31
31
|
_register_exception_handlers(app)
|
|
32
32
|
_original_openapi = app.openapi
|
|
33
|
-
app.openapi = lambda: _patched_openapi(app, _original_openapi) # type: ignore[method-assign]
|
|
33
|
+
app.openapi = lambda: _patched_openapi(app, _original_openapi) # type: ignore[method-assign] # ty:ignore[invalid-assignment]
|
|
34
34
|
return app
|
|
35
35
|
|
|
36
36
|
|
|
@@ -66,7 +66,7 @@ def configure_logging(
|
|
|
66
66
|
_SENTINEL = object()
|
|
67
67
|
|
|
68
68
|
|
|
69
|
-
def get_logger(name: str | None = _SENTINEL) -> logging.Logger: # type: ignore[assignment]
|
|
69
|
+
def get_logger(name: str | None = _SENTINEL) -> logging.Logger: # type: ignore[assignment] # ty:ignore[invalid-parameter-default]
|
|
70
70
|
"""Return a logger with the given *name*.
|
|
71
71
|
|
|
72
72
|
A thin convenience wrapper around :func:`logging.getLogger` that keeps
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Field-change monitoring via SQLAlchemy session events."""
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
+
import inspect
|
|
4
5
|
import weakref
|
|
5
6
|
from collections.abc import Awaitable
|
|
6
7
|
from enum import Enum
|
|
@@ -25,6 +26,7 @@ _SESSION_PENDING_NEW = "_ft_pending_new"
|
|
|
25
26
|
_SESSION_CREATES = "_ft_creates"
|
|
26
27
|
_SESSION_DELETES = "_ft_deletes"
|
|
27
28
|
_SESSION_UPDATES = "_ft_updates"
|
|
29
|
+
_SESSION_SAVEPOINT_DEPTH = "_ft_sp_depth"
|
|
28
30
|
|
|
29
31
|
|
|
30
32
|
class ModelEvent(str, Enum):
|
|
@@ -65,6 +67,14 @@ def _snapshot_column_attrs(obj: Any) -> dict[str, Any]:
|
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
|
|
70
|
+
def _get_watched_fields(cls: type) -> list[str] | None:
|
|
71
|
+
"""Return the watched fields for *cls*, walking the MRO to inherit from parents."""
|
|
72
|
+
for klass in cls.__mro__:
|
|
73
|
+
if klass in _WATCHED_FIELDS:
|
|
74
|
+
return _WATCHED_FIELDS[klass]
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
|
|
68
78
|
def _upsert_changes(
|
|
69
79
|
pending: dict[int, tuple[Any, dict[str, dict[str, Any]]]],
|
|
70
80
|
obj: Any,
|
|
@@ -83,6 +93,22 @@ def _upsert_changes(
|
|
|
83
93
|
pending[key] = (obj, changes)
|
|
84
94
|
|
|
85
95
|
|
|
96
|
+
@event.listens_for(AsyncSession.sync_session_class, "after_transaction_create")
|
|
97
|
+
def _after_transaction_create(session: Any, transaction: Any) -> None:
|
|
98
|
+
if transaction.nested:
|
|
99
|
+
session.info[_SESSION_SAVEPOINT_DEPTH] = (
|
|
100
|
+
session.info.get(_SESSION_SAVEPOINT_DEPTH, 0) + 1
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@event.listens_for(AsyncSession.sync_session_class, "after_transaction_end")
|
|
105
|
+
def _after_transaction_end(session: Any, transaction: Any) -> None:
|
|
106
|
+
if transaction.nested:
|
|
107
|
+
depth = session.info.get(_SESSION_SAVEPOINT_DEPTH, 0)
|
|
108
|
+
if depth > 0: # pragma: no branch
|
|
109
|
+
session.info[_SESSION_SAVEPOINT_DEPTH] = depth - 1
|
|
110
|
+
|
|
111
|
+
|
|
86
112
|
@event.listens_for(AsyncSession.sync_session_class, "after_flush")
|
|
87
113
|
def _after_flush(session: Any, flush_context: Any) -> None:
|
|
88
114
|
# New objects: capture references while session.new is still populated.
|
|
@@ -102,7 +128,7 @@ def _after_flush(session: Any, flush_context: Any) -> None:
|
|
|
102
128
|
continue
|
|
103
129
|
|
|
104
130
|
# None = not in dict = watch all fields; list = specific fields only
|
|
105
|
-
watched =
|
|
131
|
+
watched = _get_watched_fields(type(obj))
|
|
106
132
|
changes: dict[str, dict[str, Any]] = {}
|
|
107
133
|
|
|
108
134
|
attrs = (
|
|
@@ -169,7 +195,7 @@ def _schedule_with_snapshot(
|
|
|
169
195
|
_sa_set_committed_value(obj, key, value)
|
|
170
196
|
try:
|
|
171
197
|
result = fn(*args)
|
|
172
|
-
if
|
|
198
|
+
if inspect.isawaitable(result):
|
|
173
199
|
await result
|
|
174
200
|
except Exception as exc:
|
|
175
201
|
_logger.error(_CALLBACK_ERROR_MSG, exc_info=exc)
|
|
@@ -180,12 +206,24 @@ def _schedule_with_snapshot(
|
|
|
180
206
|
|
|
181
207
|
@event.listens_for(AsyncSession.sync_session_class, "after_commit")
|
|
182
208
|
def _after_commit(session: Any) -> None:
|
|
209
|
+
if session.info.get(_SESSION_SAVEPOINT_DEPTH, 0) > 0:
|
|
210
|
+
return
|
|
211
|
+
|
|
183
212
|
creates: list[Any] = session.info.pop(_SESSION_CREATES, [])
|
|
184
213
|
deletes: list[Any] = session.info.pop(_SESSION_DELETES, [])
|
|
185
214
|
field_changes: dict[int, tuple[Any, dict[str, dict[str, Any]]]] = session.info.pop(
|
|
186
215
|
_SESSION_UPDATES, {}
|
|
187
216
|
)
|
|
188
217
|
|
|
218
|
+
if creates and deletes:
|
|
219
|
+
transient_ids = {id(o) for o in creates} & {id(o) for o in deletes}
|
|
220
|
+
if transient_ids:
|
|
221
|
+
creates = [o for o in creates if id(o) not in transient_ids]
|
|
222
|
+
deletes = [o for o in deletes if id(o) not in transient_ids]
|
|
223
|
+
field_changes = {
|
|
224
|
+
k: v for k, v in field_changes.items() if k not in transient_ids
|
|
225
|
+
}
|
|
226
|
+
|
|
189
227
|
if not creates and not deletes and not field_changes:
|
|
190
228
|
return
|
|
191
229
|
|
|
@@ -129,7 +129,8 @@ async def create_worker_database(
|
|
|
129
129
|
worker_url = worker_database_url(
|
|
130
130
|
database_url=database_url, default_test_db=default_test_db
|
|
131
131
|
)
|
|
132
|
-
worker_db_name
|
|
132
|
+
worker_db_name = make_url(worker_url).database
|
|
133
|
+
assert worker_db_name is not None
|
|
133
134
|
|
|
134
135
|
engine = create_async_engine(database_url, isolation_level="AUTOCOMMIT")
|
|
135
136
|
try:
|
|
@@ -165,18 +165,18 @@ class PaginatedResponse(BaseResponse, Generic[DataT]):
|
|
|
165
165
|
|
|
166
166
|
_discriminated_union_cache: ClassVar[dict[Any, Any]] = {}
|
|
167
167
|
|
|
168
|
-
def __class_getitem__( #
|
|
168
|
+
def __class_getitem__( # ty:ignore[invalid-method-override]
|
|
169
169
|
cls, item: type[Any] | tuple[type[Any], ...]
|
|
170
170
|
) -> type[Any]:
|
|
171
171
|
if cls is PaginatedResponse and not isinstance(item, TypeVar):
|
|
172
172
|
cached = cls._discriminated_union_cache.get(item)
|
|
173
173
|
if cached is None:
|
|
174
174
|
cached = Annotated[
|
|
175
|
-
Union[CursorPaginatedResponse[item], OffsetPaginatedResponse[item]], #
|
|
175
|
+
Union[CursorPaginatedResponse[item], OffsetPaginatedResponse[item]], # ty:ignore[invalid-type-form]
|
|
176
176
|
Field(discriminator="pagination_type"),
|
|
177
177
|
]
|
|
178
178
|
cls._discriminated_union_cache[item] = cached
|
|
179
|
-
return cached #
|
|
179
|
+
return cached # ty:ignore[invalid-return-type]
|
|
180
180
|
return super().__class_getitem__(item)
|
|
181
181
|
|
|
182
182
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/cli/commands/__init__.py
RENAMED
|
File without changes
|
{fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/cli/commands/fixtures.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/exceptions/__init__.py
RENAMED
|
File without changes
|
{fastapi_toolsets-2.4.1 → fastapi_toolsets-2.4.3}/src/fastapi_toolsets/exceptions/exceptions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|