✨ feat: add unit of work abstraction - #9600
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #9600 +/- ##
==========================================
+ Coverage 88.16% 89.77% +1.60%
==========================================
Files 1563 1241 -322
Lines 60730 48047 -12683
Branches 1586 684 -902
==========================================
- Hits 53541 43132 -10409
+ Misses 6767 4759 -2008
+ Partials 422 156 -266
Continue to review full report in Codecov by Harness.
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
Introduces a backend-neutral unit-of-work contract and a SQLAlchemy/PostgreSQL implementation.
Changes:
- Adds read and transactional UoW abstractions.
- Implements connection acquisition, reuse, commit, and rollback.
- Adds focused contract and lifecycle tests.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
packages/common-library/src/common_library/unit_of_work.py |
Defines UoW contracts. |
packages/common-library/tests/test_unit_of_work.py |
Tests abstract contracts and scope reuse. |
packages/postgres-database/src/simcore_postgres_database/unit_of_work.py |
Implements SQLAlchemy-backed scopes. |
packages/postgres-database/tests/unit_of_work/conftest.py |
Provides test fixtures and scope fakes. |
packages/postgres-database/tests/unit_of_work/test_sqlalchemy_unit_of_work.py |
Tests lifecycle, reuse, validation, and rollback. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
sanderegg
left a comment
There was a problem hiding this comment.
I am not sure I see the advantage over packages/postgres-database/src/simcore_postgres_database/utils_repos.py
I find it:
- less readable
if you want to pass around the connection, now instead of passing the engine you pass the unitofwork + you need to have the right one I guess depending if you are using read or write.
does this command pattern only apply to sql? or you plan to use that elsewhere? and if yes to what?
| *, | ||
| existing: ReadUnitOfWork | None = None, | ||
| ) -> AbstractAsyncContextManager[ReadUnitOfWork]: | ||
| return _read_scope(self.engine, existing) |
There was a problem hiding this comment.
besides the fact that this looks a bit more complex to read, what is the difference with using
pass_or_acquire_connection ? from packages/postgres-database/src/simcore_postgres_database/utils_repos.py ?
There was a problem hiding this comment.
It's totally wrong to bring low-level concepts at service layer.
There was a problem hiding this comment.
I see, this is not an answer.
So this is just a wrapper around the async engine used as transaction or as connect to bring it up one level and not see asyncpg anywhere in the imports?
technically speaking this changes nothing else. unless this is applied in a generic manner to other dependencies such as redis, celery, ...
in terms of performance I doubt this will improve it as this creates more classes, calls, etc etc. not sure python excels at that.
a downside is that then if I have code that access the DB to read and optionally need to write I will now need 2 different objects instead of one engine. Why not have just one abstraction then?
There was a problem hiding this comment.
also please if this gets in, then do a complete migration and not a partial one.
| *, | ||
| existing: TransactionalUnitOfWork | None = None, | ||
| ) -> AbstractAsyncContextManager[TransactionalUnitOfWork]: | ||
| return _transaction_scope(self.engine, existing) |
There was a problem hiding this comment.
same question. what is the difference to using
transaction_context from packages/postgres-database/src/simcore_postgres_database/utils_repos.py?
pcrespov
left a comment
There was a problem hiding this comment.
As you know, this is an abstraction I'm quite fond of. I like abstractions, but if we only have ONE implementation, we risk designing it to fit that single case rather than remaining truly abstract.
Could you create a Unit of Work implementation for redis and one for filesystem?
Also, could you clarify how utilities like pass_or_acquire_connection and transaction_context fit into this? Does this mean we deprecate them, or can they be reused within the UoW?
|
So, |
|



What do these changes do?
This pull request introduces a new, abstract unit-of-work (UoW) contract for managing database read and transactional scopes, and provides a concrete implementation for asyncpg/Postgres using SQLAlchemy.
Usage example
Construct the concrete factory at the application composition boundary:
Keep orchestration backend-neutral and open the scope only around contiguous database work:
Only the postgres repository unwraps the backend-specific connection:
For writes, use
unit_of_work_factory.transaction(). Nested write operations receive and reuse the resultingTransactionalUnitOfWork; the outer scope owns commit or rollback.Related issue/s
How to test
Dev-ops