Add atomic transaction support: per-method session/commit parameters and transaction() context manager#28
Add atomic transaction support: per-method session/commit parameters and transaction() context manager#28
session/commit parameters and transaction() context manager#28Conversation
…atomic transaction support Co-authored-by: hsluoyz <3787410+hsluoyz@users.noreply.github.com>
session and commit parameters for atomic transaction support
|
@copilot fix linter |
Co-authored-by: hsluoyz <3787410+hsluoyz@users.noreply.github.com>
Fixed in c0060d7 — ran |
|
@UnoYakshi can you test this PR? |
|
@hsluoyz, I'll try in a couple of days. |
IssueI see a big issue with docs/abstraction. Now we have to work with adapter directly instead of using enforcer. await enforcer.add_policy(...)becomes # Or import adapter instead of enforcer,
# which is a bit odd, considering the naming...
adapter = enforcer.get_adapter()
await adapter.add_policy(..., session=async_session, commit=False)
await async_session.commit()Potential Solution?Ideal case, IMO, would be to have await enforcer.add_policy(..., session=async_session, commit=False)
await async_session.commit() |
|
@copilot # Issue await enforcer.add_policy(...)becomes # Or import adapter instead of enforcer,
# which is a bit odd, considering the naming...
adapter = enforcer.get_adapter()
await adapter.add_policy(..., session=async_session, commit=False)
await async_session.commit()Potential Solution?Ideal case, IMO, would be to have await enforcer.add_policy(..., session=async_session, commit=False)
await async_session.commit() |
…ion control Co-authored-by: hsluoyz <3787410+hsluoyz@users.noreply.github.com>
... Added a async with adapter.transaction(session=my_session):
await enforcer.add_policy(...)
await enforcer.add_policies(...)
await my_session.commit() # or rollbackThe |
session and commit parameters for atomic transaction supportsession/commit parameters and transaction() context manager
|
It does look a bit better now. But we still use
Yes, that was exactly my point. |
|
@ It does look a bit better now. But we still use
Yes, that was exactly my point. |
@copilot |
|
I don't think Copilot was called to. |
|
@copilot It does look a bit better now. But we still use
Yes, that was exactly my point. |
…er() pattern Co-authored-by: hsluoyz <3787410+hsluoyz@users.noreply.github.com>
... Updated the README in 259963a. The enforcer already has a adapter = enforcer.get_adapter()
async with adapter.transaction(session=session):
await enforcer.add_policy(...)
await enforcer.add_role_for_user(...)
await session.commit()No separate import or variable needed — |
|
Well, I guess I can live with async with enforcer.get_adapter().transaction(session=session):
await enforcer.add_policy(...)
await enforcer.add_role_for_user(...)
await session.commit()The only thing left bothering me is using yet another I'm just nitpicking. Should we merge, though?.. |
|
BTW, do we remove rules from enforcer if rollback occurs? I mean manually vs. it's handled in Enforcer+Adapter implicitly. |
Every adapter write method previously opened and committed its own session, making it impossible to atomically combine Casbin policy changes with other application DB operations (e.g. creating a user and assigning their policies in one transaction).
Changes
adapter.py_session_scope(commit=True, session=None)— accepts an explicitsession(yielded as-is, no commit/rollback) and acommitflag (controls auto-commit on internally-managed sessions). Constructor-leveldb_sessionbehaviour is unchanged._save_policy_line— simplified from async dual-mode to a synchronous helper that always takes a session; callers own the session scope.session: Optional[AsyncSession] = Noneandcommit: bool = Truekeyword parameters (fully backward-compatible):add_policy,add_policiesremove_policy,remove_policies,remove_filtered_policyupdate_policy,update_policies,update_filtered_policiesupdate_policiesopens one session scope and threads it through eachupdate_policycall._update_filtered_policiespropagatessession/committo its internalremove_policiesandadd_policiescalls.transaction(session=None, commit=True)— new async context manager that usescontextvars.ContextVar(per-asyncio-task, concurrency-safe) to bind a session to all adapter operations within the block. The adapter can be retrieved directly from an existing enforcer viaenforcer.get_adapter(), so no separate import or variable is needed:README.mdtransaction()context manager as the primary pattern, showingenforcer.get_adapter()to retrieve the adapter without a separate import.transaction()→ single commit covers everything).session/commitparameters for direct adapter calls.db_sessionoption as a sub-section for backward compatibility.tests/test_external_session.pysession/commitparams.transaction()context manager: commit, rollback, mixed operations, and asyncio task isolation._make_adapter(),_load_enforcer(), and_make_enforcer()helpers to reduce setup duplication.Example
Alternatively, for direct adapter calls:
Original prompt
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.