"""Helper functions and classes for picodi."""from__future__importannotationsfromtypingimport(TYPE_CHECKING,Any,AsyncContextManager,ContextManager,Generic,TypeVar,)frompicodiimportRegistryfrompicodiimportregistryasdefault_registryifTYPE_CHECKING:fromcollections.abcimportAsyncGenerator,Callable,Coroutine,Generatorsentinel=object()classPathNotFoundError(Exception):def__init__(self,current_path:str,obj:Any):self.current_path=current_pathself.obj=objsuper().__init__(f"Can't find path '{current_path}' in {type(obj)} object")
[docs]defget_value(path:str,obj:Any,*,default:Any=sentinel)->Any:""" Get attribute from nested objects. Can be useful to avoid passing entire objects like settings to functions that only need a small part of it. :param path: path to the attribute, separated by dots. :param obj: object to search the attribute in. :param default: default value to return if the attribute is not found. If not provided, an :exc:`PathNotFoundError` is raised. :raises PathNotFoundError: if the path is not found in the object and default is not provided. Example ------- .. code-block:: python obj = SimpleNamespace(foo=SimpleNamespace(bar={"baz": 42})) get_value("foo.bar.baz", obj) # Output: 42 get_value("foo.bar.baz2", obj) # Output: AttributeError get_value("foo.bar.baz2", obj, default=12) # Output: 12 """ifnotpath:raiseValueError("Empty path")ifnotisinstance(path,str):raiseTypeError("Path must be a string")value=objcurrent_path=[]forpartinpath.split("."):current_path.append(part)curr_val=_get_attr(value,part)ifcurr_valissentinel:curr_val=_get_item(value,part)ifcurr_valissentinel:ifdefaultissentinel:raisePathNotFoundError(".".join(current_path),obj)returndefaultvalue=curr_valreturnvalue
[docs]classresolve(Generic[T]):# noqa: N801""" Create a context manager from a dependency. Can be handy for testing or when you need to create "fabric" dependency (dependency that creates other dependencies based on some conditions). :param dependency: dependency to create a context manager from. Like with :func:`Provide` - don't call the dependency function here, just pass it. :return: sync or async context manager. Example ------- .. code-block:: python from picodi.helpers import resolve def get_42(): yield 42 with resolve(get_42) as val: assert val == 42 """def__init__(self,dependency:Callable[[],Coroutine[T,None,None]|Generator[T]|AsyncGenerator[T]|T,],registry:Registry|None=None,)->None:self.dependency=dependencyregistry=registryordefault_registryself.sync_cm:ContextManager[T]=registry.resolve(dependency,# type: ignore[arg-type])self.async_cm:AsyncContextManager[T]=registry.aresolve(dependency,# type: ignore[arg-type])def__enter__(self)->T:returnself.sync_cm.__enter__()def__exit__(self,*args:Any)->bool|None:returnself.sync_cm.__exit__(*args)asyncdef__aenter__(self)->T:returnawaitself.async_cm.__aenter__()asyncdef__aexit__(self,*args:Any)->bool|None:returnawaitself.async_cm.__aexit__(*args)