def test_func_name_partials_vs_human(): assert base.func_name(fnt.partialmethod(_Foo.foo), human=1, partials=0) == "foo" assert base.func_name(fnt.partialmethod(_Foo.foo), human=None, partials=0) == "foo" assert (base.func_name(fnt.partialmethod(_Foo.foo), human=1, partials=1) == "foo(...)") assert (base.func_name(fnt.partialmethod(_Foo.foo), human=None, partials=1) == "foo(...)") assert (base.func_name(fnt.partialmethod(_Foo.foo), human=1, partials=None) == "foo(...)")
def harvest(self, *items: Any, base_modules=...) -> List[Tuple[str, Callable]]: """ Collect any callable `items` and children, respecting `base_modules`, `excludes` etc. :param items: module fqdn (if already imported), items with ``__name__``, like modules, classes, functions, or partials (without ``__name__``). If nothing is given, `attr:`baseModules` is used in its place. .. Note:: This parameter works differently from :attr:`base_modules`, that is, harvesting is not limited to those modules only, recursing to any imported ones from `items`. :return: the :attr:`collected` """ old_base_modules = self.base_modules try: if base_modules is not ...: self.base_modules = base_modules if not items: items = self.base_modules # type: ignore for bi in items: if isinstance(bi, str): bi, name_path = sys.modules[bi], bi else: name_path = tuple( func_name(bi, mod=0, fqdn=0, human=0, partials=1).split(".")) self._harvest(name_path, (bi, )) return self.collected finally: self.base_modules = old_base_modules
def _make_py_item_url(fn): if not inspect.isbuiltin(fn): fn_name = func_name(fn, None, mod=1, fqdn=1, human=0) if fn_name: return f"../reference.html#{fn_name}"
def yield_wrapped_ops( self, fn: Union[Callable, Tuple[Union[str, Collection[str]], Union[Callable, Collection[Callable]]], ], exclude=(), domain: Union[str, int, Collection] = None, ) -> Iterable[FnOp]: """ Convert a (possibly **@autographed**) function into an graphtik **FnOperations**, respecting any configured overrides :param fn: either a callable, or a 2-tuple(`name-path`, `fn-path`) for:: [module[, class, ...]] callable - If `fn` is an operation, yielded as is (found also in 2-tuple). - Both tuple elements may be singulars, and are auto-tuple-zed. - The `name-path` may (or may not) correspond to the given `fn-path`, and is used to derrive the operation-name; If not given, the function name is inspected. - The last elements of the `name-path` are overridden by names in decorations; if the decor-name is the "default" (`None`), the `name-path` becomes the op-name. - The `name-path` is not used when matching overrides. :param exclude: a list of decor-names to exclude, as stored in decors. Ignored if `fn` already an operation. :param domain: if given, overrides :attr:`domain` for :func:`.autographed` decorators to search. List-ified if a single str, :func:`autographed` decors for the 1st one matching are used. :return: one or more :class:`FnOp` instances (if more than one name is defined when the given function was :func:`autographed`). Overriddes order: my-args, self.overrides, autograph-decorator, inspection See also: David Brubeck Quartet, "40 days" """ if isinstance(fn, tuple): name_path, fn_path = fn else: name_path, fn_path = (), fn fun_path = cast(Tuple[Callable, ...], astuple(fn_path, None)) fun = fun_path[-1] if isinstance(fun, Operation): ## pass-through operations yield fun return def param_to_modifier(name: str, param: inspect.Parameter) -> str: return (optional(name) # is optional? if param.default is not inspect._empty # type: ignore else keyword(name) if param.kind == Parameter.KEYWORD_ONLY else name) given_name_path = astuple(name_path, None) decors_by_name = get_autograph_decors(fun, {}, domain or self.domain) for decor_name, decors in decors_by_name.items() or ((None, {}), ): if given_name_path and not decor_name: name_path = decor_path = given_name_path else: # Name in decors was "default"(None). name_path = decor_path = astuple( (decor_name if decor_name else func_name(fun, fqdn=1)).split("."), None, ) assert decor_path, locals() if given_name_path: # Overlay `decor_path` over `named_path`, right-aligned. name_path = tuple(*name_path[:-len(decor_path)], *decor_path) fn_name = str(name_path[-1]) if fn_name in exclude: continue overrides = self._from_overrides(decor_path) op_data = (ChainMap(overrides, decors) if (overrides and decors) else overrides if overrides else decors) if op_data: log.debug("Autograph overrides for %r: %s", name_path, op_data) op_props = "needs provides renames, inp_sideffects out_sideffects".split( ) needs, provides, override_renames, inp_sideffects, out_sideffects = ( op_data.get(a, _unset) for a in op_props) sig = None if needs is _unset: sig = inspect.signature(fun) needs = [ param_to_modifier(name, param) for name, param in sig.parameters.items() if name != "self" and param.kind is not Parameter.VAR_KEYWORD ] ## Insert object as 1st need for object-methods. # if len(fun_path) > 1: clazz = fun_path[-2] # TODO: respect autograph decorator for object-names. class_name = name_path[-2] if len( name_path) > 1 else clazz.__name__ if is_regular_class(class_name, clazz): log.debug("Object-method %s.%s", class_name, fn_name) needs.insert(0, camel_2_snake_case(class_name)) needs = aslist(needs, "needs") if ... in needs: if sig is None: sig = inspect.signature(fun) needs = [ arg_name if n is ... else n for n, arg_name in zip(needs, sig.parameters) ] if provides is _unset: if is_regular_class(fn_name, fun): ## Convert class-name into object variable. provides = camel_2_snake_case(fn_name) elif self.out_patterns: provides = self._deduce_provides_from_fn_name( fn_name) or _unset if provides is _unset: provides = () provides = aslist(provides, "provides") needs, provides = self._apply_renames( (override_renames, self.renames), (needs, provides)) if inp_sideffects is not _unset: needs.extend((i if is_sfx(i) else sfxed( *i) if isinstance(i, tuple) else sfx(i)) for i in aslist(inp_sideffects, "inp_sideffects")) if out_sideffects is not _unset: provides.extend( (i if is_sfx(i) else sfxed( *i) if isinstance(i, tuple) else sfx(i)) for i in aslist(out_sideffects, "out_sideffects")) if self.full_path_names: fn_name = self._join_path_names(*name_path) op_kws = self._collect_rest_op_args(decors) yield FnOp(fn=fun, name=fn_name, needs=needs, provides=provides, **op_kws)
def test_func_name_lambda_local(kw, exp): assert base.func_name(lambda: None, **kw) == exp
def test_func_name_object_method(kw, exp): assert base.func_name(_Foo().foo, **kw) == exp
def test_func_name_partial_method(kw, exp): assert base.func_name(fnt.partialmethod(_Foo.foo), **kw) == exp
def test_func_name_class_method(kw, exp): assert base.func_name(_Foo.foo, **kw) == exp
def test_func_name_partial_x2(kw, exp): assert base.func_name(fnt.partial(fnt.partial(_foo, 1, a=2), b=3), **kw) == exp
def test_func_name_partial_empty(kw, exp): assert base.func_name(fnt.partial(_foo), **kw) == exp
def test_func_name_non_partial(kw, exp): assert base.func_name(_foo, **kw) == exp
def test_func_name_builtin(kw, exp): assert base.func_name(eval, **kw) == exp