dump-things-server/dump_things_service/patches/__init__.py
2025-09-30 08:57:44 +02:00

72 lines
1.9 KiB
Python

# Taken from datalad_next.patches
from __future__ import annotations
import logging
from importlib import import_module
from typing import Any
lgr = logging.getLogger('dump_things_service')
def apply_patch(
modname: str,
objname: str | None,
attrname: str,
patch: Any,
msg: str | None = None,
*,
expect_attr_present: bool = True,
):
"""
Monkey patch helper
Parameters
----------
modname: str
Importable name of the module with the patch target.
objname: str or None
If `None`, patch will target an attribute of the module,
otherwise an object name within the module can be specified.
attrname: str
Name of the attribute to replace with the patch, either
in the module or the given object (see ``objname``)
patch:
The identified attribute will be replaced with this object.
msg: str or None
If given, a debug-level log message with this text will be
emitted, otherwise a default message is generated.
expect_attr_present: bool
If True (default) an exception is raised when the target
attribute is not found.
Returns
-------
object
The original, unpatched attribute -- if ``expect_attr_present``
is enabled, or ``None`` otherwise.
Raises
------
ImportError
When the target module cannot be imported
AttributedError
When the target object is not found, or the target attribute is
not found.
"""
orig_attr = None
msg = msg or f'Apply patch to {modname}.{attrname}'
lgr.debug(msg)
# we want to fail on ImportError
mod = import_module(modname)
obj = getattr(mod, objname) if objname else mod
if expect_attr_present:
# we want to fail on a missing attribute/object
orig_attr = getattr(obj, attrname)
setattr(obj, attrname, patch)
return orig_attr