Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

uow: import from invenio_db #596

Merged
merged 2 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 13 additions & 155 deletions invenio_records_resources/services/uow.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

"""Unit of work.

Classes were moved to invenio-db.

Used to group multiple service operations into a single atomic unit. The Unit
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All these comments should probably be removed from here to avoid duplication (and one of them getting stale).
Maybe something like: For documentation see ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are some parts which are specific to the classes that remained in this module and in invenio_db I modified the comment so it's only about the functionalities that I copied over and can be used from there.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I thought it was an exact copy-paste, but I now see that it's been adapted.
I think it's fine, this part of the code did not change much over the years, and people should think about updating if there are every big changes.

of Work maintains a list of operations and coordinates the commit, indexing and
task execution.
Expand Down Expand Up @@ -101,68 +103,25 @@ def on_commit(self, uow):
# ... executed after the database transaction commit ...
"""


from functools import wraps

from celery import current_app
from invenio_db import db

# backwards compatible imports
from invenio_db.uow import (
ModelCommitOp,
ModelDeleteOp,
Operation,
UnitOfWork,
unit_of_work,
)

from ..tasks import send_change_notifications

__all__ = ["ModelCommitOp", "ModelDeleteOp", "Operation", "UnitOfWork", "unit_of_work"]


#
# Unit of work operations
#
class Operation:
"""Base class for unit of work operations."""

def on_register(self, uow):
"""Called upon operation registration."""
pass

def on_commit(self, uow):
"""Called in the commit phase (after the transaction is committed)."""
pass

def on_post_commit(self, uow):
"""Called right after the commit phase."""
pass

def on_rollback(self, uow):
"""Called in the rollback phase (after the transaction rollback)."""
pass

def on_post_rollback(self, uow):
"""Called right after the rollback phase."""
pass


class ModelCommitOp(Operation):
"""SQLAlchemy model add/update operation."""

def __init__(self, model):
"""Initialize the commit operation."""
super().__init__()
self._model = model

def on_register(self, uow):
"""Add model to db session."""
uow.session.add(self._model)


class ModelDeleteOp(Operation):
"""SQLAlchemy model delete operation."""

def __init__(self, model):
"""Initialize the set delete operation."""
super().__init__()
self._model = model

def on_register(self, uow):
"""Delete model."""
uow.session.delete(self._model)


class RecordCommitOp(Operation):
"""Record commit operation with indexing."""

Expand Down Expand Up @@ -325,104 +284,3 @@ def on_post_commit(self, uow):
self._record_type,
[(r.pid.pid_value, str(r.id), r.revision_id) for r in self._records],
)


#
# Unit of work context manager
#
class UnitOfWork:
"""Unit of work context manager.

Note, the unit of work does not currently take duplication of work into
account. Thus, you can e.g. add two record commit operations of the same
record which will then index the record twice, even though only one time
is needed.
"""

def __init__(self, session=None):
"""Initialize unit of work context."""
self._session = session or db.session
self._operations = []
self._dirty = False

def __enter__(self):
"""Entering the context."""
return self

def __exit__(self, exc_type, *args):
"""Rollback on exception."""
if exc_type is not None:
self.rollback()
self._mark_dirty()

@property
def session(self):
"""The SQLAlchemy database session associated with this UoW."""
return self._session

def _mark_dirty(self):
"""Mark the unit of work as dirty."""
if self._dirty:
raise RuntimeError("The unit of work is already committed or rolledback.")
self._dirty = True

def commit(self):
"""Commit the unit of work."""
self.session.commit()
# Run commit operations
for op in self._operations:
op.on_commit(self)
# Run post commit operations
for op in self._operations:
op.on_post_commit(self)
self._mark_dirty()

def rollback(self):
"""Rollback the database session."""
self.session.rollback()
# Run rollback operations
for op in self._operations:
op.on_rollback(self)
# Run post rollback operations
for op in self._operations:
op.on_post_rollback(self)

def register(self, op):
"""Register an operation."""
# Run on register
op.on_register(self)
# Append to list of operations.
self._operations.append(op)


def unit_of_work(**kwargs):
"""Decorator to auto-inject a unit of work if not provided.

If no unit of work is provided, this decorator will create a new unit of
work and commit it after the function has been executed.

.. code-block:: python

@unit_of_work()
def aservice_method(self, ...., uow=None):
# ...
uow.register(...)

"""

def decorator(f):
@wraps(f)
def inner(self, *args, **kwargs):
if "uow" not in kwargs:
# Migration path - start a UoW and commit
with UnitOfWork(db.session) as uow:
kwargs["uow"] = uow
res = f(self, *args, **kwargs)
uow.commit()
return res
else:
return f(self, *args, **kwargs)

return inner

return decorator
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ install_requires =
flask-resources>=1.0.0,<2.0.0
invenio-accounts>=5.0.0,<6.0.0
invenio-base>=1.3.0,<2.0.0
invenio-db>=1.2.0,<2.0.0
invenio-files-rest>=2.0.0,<3.0.0
invenio-i18n>=2.0.0,<3.0.0
invenio-indexer>=2.1.0,<3.0.0
Expand Down