Source code for pytest_object_getter.fixtures

# pylint: disable=redefined-outer-name
import pytest


[docs]@pytest.fixture def attribute_getter(): from typing import Any class AttributeGetter(object): def __init__(self, debug_message_factory): self.debug_message_factory = debug_message_factory def __call__(self, object_ref: Any, attribute: str) -> Any: object_reference = getattr(object_ref, attribute) if object_reference is None: raise RuntimeError(self.debug_message_factory(object_ref, attribute)) return object_reference return AttributeGetter
[docs]@pytest.fixture def generic_object_getter_class(attribute_getter, monkeypatch): """Class instances can extract a requested object from within a module and optionally patch any object in the module's namespace at runtime.""" from importlib import import_module from typing import Any, Generic, TypeVar T = TypeVar('T') class AbstractGenericObjectGetter(Generic[T]): def __init__(self, debug_message=None): self._get_object_callback = { True: self._get_production_object, False: self._build_object, } if debug_message: self._attr_getter = attribute_getter( lambda _object, name: "{msg}. Did not find {name} on object of type {type}".format( msg=debug_message, name=name, type=type(_object).__name__ ) ) else: self._attr_getter = attribute_getter( lambda _object, name: "Did not find {name} on object of type {type}".format( name=name, type=type(_object).__name__ ) ) def __call__(self, *args: Any, **kwargs: Any) -> Any: return self.get(*args, **kwargs) def get(self, request: T, overrides={}): d = {'overrides': overrides} use_production_object: bool = not bool(d.get('overrides')) return self._get_object_callback[use_production_object](request, **d) def _get_production_object(self, request: T, **kwargs): object_module = self._get_object_module(request) computed_object = self._get_object(request, object_module) return computed_object def _build_object(self, request: T, overrides={}): object_module = self._get_object_module(request) for symbol_name, factory in overrides.items(): monkeypatch.setattr(object_module, symbol_name, factory()) computed_object = self._get_object(request, object_module) return computed_object def _get_object_module(self, request: T): return import_module(self._extract_module_string(request)) def _extract_module_string(self, request: T) -> str: """Extract the module 'path' from the request as dot (.) separated words (module/subpackages names).""" raise NotImplementedError def _extract_object_symbol_name(self, request: T) -> str: """Extract the name of the reference (symbol in code) that points to the object requested for getting at runtime.""" raise NotImplementedError def _get_object(self, request: T, object_module): return self._attr_getter(object_module, self._extract_object_symbol_name(request)) return AbstractGenericObjectGetter
[docs]@pytest.fixture def object_getter_class(generic_object_getter_class): """Do a dynamic import of a module and get an object from its namespace. This fixture returns a Python Class that can do a dynamic import of a module and get an object from its namespace. Instances of this class are callable's (they implement the __call__ protocol ) and uppon calling the return a reference to the object "fetched" from the namespace. Callable instances arguments: * 1st: object with the 'symbol_namel': str and 'object_module_string': str attributes expected "on it" Returns: ObjectGetter: Class that can do a dynamic import and get an object """ from abc import ABC, abstractmethod class RequestLike(ABC): @property @abstractmethod def symbol_name(self) -> str: # how the object (ie a get_job method) is imported into the namespace of a module (ie a metadata_provider module) raise NotImplementedError @property @abstractmethod def object_module_string(self) -> str: # the module (in a \w+\.\w+\.\w+ kind of format) where the object reference is present/computed raise NotImplementedError class ObjectGetter(generic_object_getter_class[RequestLike]): def _extract_module_string(self, request) -> str: return request.object_module_string def _extract_object_symbol_name(self, request) -> str: return request.symbol_name return ObjectGetter
[docs]@pytest.fixture def get_object(object_getter_class): """Import an object from a module and optionally mock any object in its namespace. A callable that can import an object, given a reference (str), from a module , given its "path" (string represented as 'dotted' modules: same way python code imports modules), and provide the capability to monkeypatch/mock any object found in the module's namespace at runtime. The client code must supply the first 2 arguments at runtime, correspoding to the object's symbol name (str) and module "path" (str). The client code can optionally use the 'overrides' kwarg to supply a python dictionary to specify what runtime objects to mock and how. Each dictionary entry should model your intention to monkeypatch one of the module namespace' objects with a custom 'mock' value. Each dictionary key should be a string corresponding to an object's reference name (present in the module's namespace) and each value should be a callable that can construct the 'mock' value. The callable should take no arguments and acts as a "factory", that when called should provide the 'mock' value. Example: def mocked_request_get() business_method = get_object( "business_method", "business_package.methods", overrides={"production": lambda: 'mocked'} ) Args: symbol (str): the object's reference name module (str): the module 'path' represented as module names "joined" by "." (dots) overrides (dict, optional): declare what to monkeypatch and with what "mocks". Defaults to None. Returns: Any: the object imported from the module with its namespace potentially mocked """ class ObjectGetterAdapter(object_getter_class): """Adapter Class of the ObjectGetter class, see object_getter_class fixture. Returns: ObjectGetterAdapter: the Adapter Class """ def __call__(self, symbol_ref: str, module: str, **kwargs): return super().__call__( type( "RequestLike", (), {"symbol_name": symbol_ref, "object_module_string": module}, ), **kwargs, ) return ObjectGetterAdapter()