Source code for swh.core.db.common

# Copyright (C) 2015-2019  The Software Heritage developers
# See the AUTHORS file at the top-level directory of this distribution
# License: GNU General Public License version 3, or any later version
# See top-level LICENSE file for more information

import functools
import inspect


[docs] def remove_kwargs(names): def decorator(f): sig = inspect.signature(f) params = sig.parameters params = [param for param in params.values() if param.name not in names] sig = sig.replace(parameters=params) f.__signature__ = sig return f return decorator
[docs] def apply_options(cursor, options): """Applies the given postgresql client options to the given cursor. Returns a dictionary with the old values if they changed.""" old_options = {} for option, value in options.items(): cursor.execute("SHOW %s" % option) old_value = cursor.fetchall()[0][0] if old_value != value: cursor.execute("SET LOCAL %s TO %%s" % option, (value,)) old_options[option] = old_value return old_options
[docs] def db_transaction(**client_options): """decorator to execute Backend methods within DB transactions The decorated method must accept a ``cur`` and ``db`` keyword argument Client options are passed as ``set`` options to the postgresql server. If available, decorated ``self.query_options`` can be defined as a dict which keys are (decorated) method names and values are dicts. These later dicts are merged with the given ``client_options``. So it's possible to define default client_options as decorator arguments and overload them from e.g. a configuration file (e.g. making is the ``self.query_options`` attribute filled from a config file). """ def decorator(meth, __client_options=client_options): if inspect.isgeneratorfunction(meth): raise ValueError("Use db_transaction_generator for generator functions.") @remove_kwargs(["cur", "db"]) @functools.wraps(meth) def _meth(self, *args, **kwargs): options = getattr(self, "query_options", None) or {} if meth.__name__ in options: client_options = {**__client_options, **options[meth.__name__]} else: client_options = __client_options if "cur" in kwargs and kwargs["cur"]: cur = kwargs["cur"] old_options = apply_options(cur, client_options) ret = meth(self, *args, **kwargs) apply_options(cur, old_options) return ret else: db = self.get_db() try: with db.transaction() as cur: apply_options(cur, client_options) return meth(self, *args, db=db, cur=cur, **kwargs) finally: self.put_db(db) return _meth return decorator
[docs] def db_transaction_generator(**client_options): """decorator to execute Backend methods within DB transactions, while returning a generator The decorated method must accept a ``cur`` and ``db`` keyword argument Client options are passed as ``set`` options to the postgresql server. If available, decorated ``self.query_options`` can be defined as a dict which keys are (decorated) method names and values are dicts. These later dicts are merged with the given ``client_options``. So it's possible to define default client_options as decorator arguments and overload them from e.g. a configuration file (e.g. making is the ``self.query_options`` attribute filled from a config file). """ def decorator(meth, __client_options=client_options): if not inspect.isgeneratorfunction(meth): raise ValueError("Use db_transaction for non-generator functions.") @remove_kwargs(["cur", "db"]) @functools.wraps(meth) def _meth(self, *args, **kwargs): options = getattr(self, "query_options", None) or {} if meth.__name__ in options: client_options = {**__client_options, **options[meth.__name__]} else: client_options = __client_options if "cur" in kwargs and kwargs["cur"]: cur = kwargs["cur"] old_options = apply_options(cur, client_options) yield from meth(self, *args, **kwargs) apply_options(cur, old_options) else: db = self.get_db() try: with db.transaction() as cur: apply_options(cur, client_options) yield from meth(self, *args, db=db, cur=cur, **kwargs) finally: self.put_db(db) return _meth return decorator