Пример #1
0
def to_str(func: ConfigKey):
    """Get a unique string for each function.

    Example:
        >>> to_str(to_str)
        'config.config.to_str'
        >>> import random
        >>> to_str(random.randint)
        'random.Random.randint'
    """
    try:
        func = _unwrap(func)
        absolute_filename = Path(inspect.getfile(func))
        # NOTE: `relative_filename` is the longest filename relative to `sys.path` paths but
        # shorter than a absolute filename.
        relative_filename = None
        for path in sys.path:
            try:
                new_filename = str(
                    absolute_filename.relative_to(Path(path).absolute()))
                if relative_filename is None:
                    relative_filename = new_filename
                elif len(new_filename) > len(relative_filename):
                    relative_filename = new_filename
            except ValueError:
                pass
        filename = str(relative_filename
                       if relative_filename is not None else absolute_filename)
        return filename.replace("/", ".")[:-3] + "." + func.__qualname__
    except TypeError:
        return "#" + func.__qualname__
Пример #2
0
def partial(func: typing.Callable[..., _PartialReturnType], *args,
            **kwargs) -> typing.Callable[..., _PartialReturnType]:
    """Get a `partial` for `func` using the global configuration."""
    key = _unwrap(func)
    if key not in _config:
        raise KeyError(f"`{key.__qualname__}` has not been configured.")
    return functools.partial(func, *args, **kwargs, **_config[key])
Пример #3
0
def add(config: Config, overwrite: bool = False):
    """Add to the global configuration.

    Args:
        config
        overwrite: Iff `True` then configurations can be overwritten.
    """
    global _config
    global _code_to_func

    [_check_args(func, args) for func, args in config.items()]

    for key_, value in config.items():
        key = _unwrap(key_)
        if not _is_builtin(key):
            setattr(key, _orginal_key, key_)
        _get_funcs(key)  # NOTE: Check `key` before adding it
        if key in _config:
            update = Args({**_config[key], **value})
            if not overwrite and len(update) != len(_config[key]) + len(value):
                for arg, val in _config[key].items():
                    if not _is_equal(update[arg], val):
                        message = f"Trying to overwrite `{key.__qualname__}#{arg}` configuration."
                        raise ValueError(message)
            _config[key] = update
        else:
            _config[key] = value.copy()

    _update_trace_globals()
Пример #4
0
def _get_func_and_arg(
    arg: typing.Optional[str] = None,
    func: typing.Optional[ConfigKey] = None,
    stack: int = 1,
) -> tuple[ConfigKey, str]:
    """Get the calling function and argument that executes this code to get it's input.

    NOTE: This may not work with PyTest, learn more:
    https://github.com/alexmojaki/executing/issues/2

    NOTE: Use `executing` until Python 3.11, learn more:
    https://github.com/alexmojaki/executing/issues/24
    https://www.python.org/dev/peps/pep-0657/
    """
    if func is not None and arg is not None:
        return func, arg

    frame = sys._getframe(stack)
    # NOTE: This was inspired by `executing.Source.__executing_cache`.
    key = (frame.f_code, id(frame.f_code), frame.f_lasti, func, arg, stack)
    try:
        return _get_func_and_arg_cache[key]
    except KeyError:
        pass

    exec_ = executing.Source.executing(frame)
    if frame.f_code.co_filename == "<stdin>":
        raise NotImplementedError("REPL is not supported.")
    assert len(exec_.statements) == 1, "Invariant failure."
    tree = next(iter(exec_.statements))
    parents = _get_child_to_parent_map(tree)
    if exec_.node is None and "pytest" in sys.modules:
        raise RuntimeError(
            "This issue may have been triggered:\n"
            "https://github.com/alexmojaki/executing/issues/2\n\n"
            "If so, please don't run this in a PyTest `assert` statement.")
    parent = parents[exec_.node]

    if arg is None:
        if isinstance(parent, ast.keyword):
            arg = parent.arg
            parent = parents[parent]

    if func is None:
        if not isinstance(parent, ast.Call):
            raise SyntaxError("Unable to find calling function.")
        func = _resolve_func(frame, parent.func)
        if func == functools.partial:
            if len(parent.args) == 0:
                raise SyntaxError("Partial doesn't have arguments.")
            func = _resolve_func(frame, parent.args[0])
        func = _unwrap(func)

    _get_func_and_arg_cache[key] = (func, arg)

    return func, arg
Пример #5
0
def _get_funcs(func: typing.Callable) -> typing.List[typing.Callable]:
    """Get a list of functions `func` may be referring to."""
    if inspect.isclass(func):
        funcs = []
        if func.__init__ not in (b.__init__ for b in func.__bases__):
            funcs.append(func.__init__)
        if func.__new__ not in (b.__new__ for b in func.__bases__):
            funcs.append(func.__new__)
        if len(funcs) == 0:
            # NOTE: An implicit configuration like this may lead to duplicate configurations.
            # This happens when the user defines a seperate configuration for a child and parent
            # object that share the same initiation. Since there is no difference between their
            # initiation functions, the configuration for them is duplicated.
            raise KeyError(
                f"The initiation for `{func}` is only implicitly defined. "
                "Please use only explicit configurations.")
    else:
        funcs = [func]
    return [_unwrap(f) for f in funcs]