sqlalchemy-foundation-kit 0.0.0__tar.gz → 0.2.0__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.
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/CHANGELOG.md +12 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/PKG-INFO +3 -3
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/README.md +1 -1
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/__init__.py +3 -1
- sqlalchemy_foundation_kit-0.2.0/sqlalchemy_foundation_kit/__version__.py +1 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/uow/__init__.py +2 -1
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/uow/protocols.py +18 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/uow/sqlalchemy.py +31 -0
- sqlalchemy_foundation_kit-0.0.0/sqlalchemy_foundation_kit/__version__.py +0 -1
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/.gitignore +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/LICENSE +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/pyproject.toml +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/_typing.py +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/base/__init__.py +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/base/_optional.py +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/base/engine.py +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/base/metadata.py +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/base/models.py +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/base/serialization.py +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/base/types.py +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/config/__init__.py +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/config/postgres.py +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/contrib/__init__.py +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/contrib/_metrics_utils.py +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/contrib/dependency_injector/__init__.py +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/contrib/dependency_injector/_base.py +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/contrib/dependency_injector/_deps.py +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/contrib/dependency_injector/database.py +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/contrib/dependency_injector/metrics.py +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/contrib/di/__init__.py +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/contrib/di/_base.py +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/contrib/di/_deps.py +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/contrib/di/database.py +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/contrib/di/metrics.py +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/contrib/metrics/__init__.py +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/contrib/metrics/postgres.py +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/contrib/settings/__init__.py +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/contrib/settings/postgres.py +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/contrib/telemetry/__init__.py +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/contrib/telemetry/instrumentations.py +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/contrib/telemetry/uow.py +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/protocols/__init__.py +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/protocols/metrics.py +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/py.typed +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/session/__init__.py +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/session/builder.py +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/session/connection.py +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/session/factories.py +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/session/locks.py +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/session/manager.py +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/session/retry.py +0 -0
- {sqlalchemy_foundation_kit-0.0.0 → sqlalchemy_foundation_kit-0.2.0}/sqlalchemy_foundation_kit/uow/enums.py +0 -0
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.0](https://github.com/bedrock-python/sqlalchemy-foundation-kit/compare/sqlalchemy-foundation-kit-v0.1.0...sqlalchemy-foundation-kit-v0.2.0) (2026-08-28)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **uow:** add savepoint() to AsyncSQLAlchemyUowTransaction ([#6](https://github.com/bedrock-python/sqlalchemy-foundation-kit/issues/6)) ([c46d881](https://github.com/bedrock-python/sqlalchemy-foundation-kit/commit/c46d881052b7476211c31f4adc6058d4f5500b6a)), closes [#5](https://github.com/bedrock-python/sqlalchemy-foundation-kit/issues/5)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **ci:** add workflow_dispatch and created trigger to publish workflow ([142aef5](https://github.com/bedrock-python/sqlalchemy-foundation-kit/commit/142aef5040777b31509e8d7b26f01774e8f22432))
|
|
14
|
+
|
|
3
15
|
## 0.1.0 (2026-05-19)
|
|
4
16
|
|
|
5
17
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: sqlalchemy-foundation-kit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Foundation layer for SQLAlchemy-based services with UoW, session management, and observability
|
|
5
5
|
Project-URL: Repository, https://github.com/bedrock-python/sqlalchemy-foundation-kit
|
|
6
6
|
Project-URL: Documentation, https://bedrock-python.github.io/sqlalchemy-foundation-kit/
|
|
@@ -462,7 +462,7 @@ class CreateUserUseCase:
|
|
|
462
462
|
### Core (always available)
|
|
463
463
|
- **Session Management**: `AsyncSessionManager`, `AsyncCConnection`
|
|
464
464
|
- **Base ORM**: `Base`, `BaseTable`, `DatetimeColumnsMixin`, `UnConstrainedEnum`, `PydanticJSONB`
|
|
465
|
-
- **Unit of Work**: `AsyncUnitOfWork`, `AsyncSQLAlchemyUnitOfWork`, `IsolationLevel`
|
|
465
|
+
- **Unit of Work**: `AsyncUnitOfWork`, `AsyncSQLAlchemyUnitOfWork`, `IsolationLevel`, `SupportsSavepoint`
|
|
466
466
|
- **Protocols**: `PostgresSettingsProtocol`, `PostgresMetricsProtocol`
|
|
467
467
|
- **Utilities**: `build_engine_kwargs`, `resolve_pool_class`, `load_orm_metadata`
|
|
468
468
|
|
|
@@ -212,7 +212,7 @@ class CreateUserUseCase:
|
|
|
212
212
|
### Core (always available)
|
|
213
213
|
- **Session Management**: `AsyncSessionManager`, `AsyncCConnection`
|
|
214
214
|
- **Base ORM**: `Base`, `BaseTable`, `DatetimeColumnsMixin`, `UnConstrainedEnum`, `PydanticJSONB`
|
|
215
|
-
- **Unit of Work**: `AsyncUnitOfWork`, `AsyncSQLAlchemyUnitOfWork`, `IsolationLevel`
|
|
215
|
+
- **Unit of Work**: `AsyncUnitOfWork`, `AsyncSQLAlchemyUnitOfWork`, `IsolationLevel`, `SupportsSavepoint`
|
|
216
216
|
- **Protocols**: `PostgresSettingsProtocol`, `PostgresMetricsProtocol`
|
|
217
217
|
- **Utilities**: `build_engine_kwargs`, `resolve_pool_class`, `load_orm_metadata`
|
|
218
218
|
|
|
@@ -58,12 +58,13 @@ from .uow import (
|
|
|
58
58
|
IsolationLevel,
|
|
59
59
|
PostgresAdvisoryLockMixin,
|
|
60
60
|
SupportsAdvisoryLock,
|
|
61
|
+
SupportsSavepoint,
|
|
61
62
|
)
|
|
62
63
|
|
|
63
64
|
try:
|
|
64
65
|
__version__ = version("sqlalchemy-foundation-kit")
|
|
65
66
|
except PackageNotFoundError: # pragma: no cover
|
|
66
|
-
__version__ = "0.
|
|
67
|
+
__version__ = "0.2.0"
|
|
67
68
|
|
|
68
69
|
__all__ = [ # noqa: RUF022
|
|
69
70
|
# Base ORM
|
|
@@ -109,6 +110,7 @@ __all__ = [ # noqa: RUF022
|
|
|
109
110
|
"IsolationLevel",
|
|
110
111
|
"PostgresAdvisoryLockMixin",
|
|
111
112
|
"SupportsAdvisoryLock",
|
|
113
|
+
"SupportsSavepoint",
|
|
112
114
|
# Version
|
|
113
115
|
"__version__",
|
|
114
116
|
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.0" # x-release-please-version
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from .enums import IsolationLevel
|
|
6
|
-
from .protocols import AsyncUnitOfWork, AsyncUowTransaction, SupportsAdvisoryLock
|
|
6
|
+
from .protocols import AsyncUnitOfWork, AsyncUowTransaction, SupportsAdvisoryLock, SupportsSavepoint
|
|
7
7
|
from .sqlalchemy import (
|
|
8
8
|
AsyncSQLAlchemyUnitOfWork,
|
|
9
9
|
AsyncSQLAlchemyUowTransaction,
|
|
@@ -18,4 +18,5 @@ __all__ = [
|
|
|
18
18
|
"IsolationLevel",
|
|
19
19
|
"PostgresAdvisoryLockMixin",
|
|
20
20
|
"SupportsAdvisoryLock",
|
|
21
|
+
"SupportsSavepoint",
|
|
21
22
|
]
|
|
@@ -32,6 +32,24 @@ class SupportsAdvisoryLock(Protocol):
|
|
|
32
32
|
...
|
|
33
33
|
|
|
34
34
|
|
|
35
|
+
class SupportsSavepoint(Protocol):
|
|
36
|
+
"""Capability protocol for transactions supporting savepoints.
|
|
37
|
+
|
|
38
|
+
Use this when a use case must keep going after one step fails — for example,
|
|
39
|
+
processing a batch of independent items where a bad item must not take the
|
|
40
|
+
rest of the batch (or the record of its own failure) down with it.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def savepoint(self) -> AbstractAsyncContextManager[None]:
|
|
44
|
+
"""Open a nested block whose failure does not poison the surrounding transaction.
|
|
45
|
+
|
|
46
|
+
On exception the block's changes are rolled back, the exception propagates
|
|
47
|
+
unchanged, and the surrounding transaction remains usable. On success the
|
|
48
|
+
block's changes stay part of the surrounding transaction.
|
|
49
|
+
"""
|
|
50
|
+
...
|
|
51
|
+
|
|
52
|
+
|
|
35
53
|
class AsyncUnitOfWork(Protocol, Generic[T_co]):
|
|
36
54
|
"""Provides transactional context for repository operations.
|
|
37
55
|
|
|
@@ -129,6 +129,37 @@ class AsyncSQLAlchemyUowTransaction(AsyncUowTransaction):
|
|
|
129
129
|
"""Get the underlying SQLAlchemy async session."""
|
|
130
130
|
return self._session
|
|
131
131
|
|
|
132
|
+
@asynccontextmanager
|
|
133
|
+
async def savepoint(self) -> AsyncIterator[None]:
|
|
134
|
+
"""Open a savepoint whose failure does not poison the surrounding transaction.
|
|
135
|
+
|
|
136
|
+
On PostgreSQL a single failed statement aborts the whole transaction: every
|
|
137
|
+
later statement on the connection fails until rollback, including the ones
|
|
138
|
+
that would record which step failed. A savepoint (``SAVEPOINT`` /
|
|
139
|
+
``ROLLBACK TO SAVEPOINT``) narrows that blast radius to this block.
|
|
140
|
+
|
|
141
|
+
On exception the block's changes are rolled back, the exception propagates
|
|
142
|
+
unchanged — the caller decides what a failed step means — and the surrounding
|
|
143
|
+
transaction stays usable. On success the savepoint is released and its changes
|
|
144
|
+
remain part of the surrounding transaction, committed or rolled back with it.
|
|
145
|
+
Savepoints nest: a ``savepoint()`` inside another rolls back only the inner block.
|
|
146
|
+
|
|
147
|
+
Intended for use inside :meth:`AsyncSQLAlchemyUnitOfWork.transaction` or
|
|
148
|
+
:meth:`AsyncSQLAlchemyUnitOfWork.managed_session`.
|
|
149
|
+
|
|
150
|
+
Example:
|
|
151
|
+
async with uow.transaction() as tx:
|
|
152
|
+
for event in await tx.outbox.list_pending():
|
|
153
|
+
try:
|
|
154
|
+
async with tx.savepoint():
|
|
155
|
+
await tx.outbox.process(event)
|
|
156
|
+
except Exception as exc:
|
|
157
|
+
await tx.outbox.mark_failed(event, reason=str(exc))
|
|
158
|
+
# Commits processed events and failure records together
|
|
159
|
+
"""
|
|
160
|
+
async with self._session.begin_nested():
|
|
161
|
+
yield
|
|
162
|
+
|
|
132
163
|
|
|
133
164
|
class PostgresAdvisoryLockMixin:
|
|
134
165
|
"""Mixin providing PostgreSQL advisory lock support for UoW transactions.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.0.0"
|
|
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
|
|
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
|
|
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
|
|
File without changes
|