Source code for domdf_python_tools.delegators

#!/usr/bin/env python
# cython: language_level=3
#
#  delegators.py
"""
Decorators for functions that delegate parts of their functionality to other functions.

.. versionadded:: 0.10.0
"""
#
#  Copyright © 2020 Dominic Davis-Foster <dominic@davis-foster.co.uk>
#
#  Permission is hereby granted, free of charge, to any person obtaining a copy
#  of this software and associated documentation files (the "Software"), to deal
#  in the Software without restriction, including without limitation the rights
#  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#  copies of the Software, and to permit persons to whom the Software is
#  furnished to do so, subject to the following conditions:
#
#  The above copyright notice and this permission notice shall be included in all
#  copies or substantial portions of the Software.
#
#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
#  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
#  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
#  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
#  DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
#  OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
#  OR OTHER DEALINGS IN THE SOFTWARE.
#
#  delegate_kwargs based on https://github.com/fastai/fastcore
#  |  Licensed under the Apache License, Version 2.0 (the "License"); you may
#  |  not use this file except in compliance with the License. You may obtain
#  |  a copy of the License at
#  |
#  |      http://www.apache.org/licenses/LICENSE-2.0
#  |
#  |  Unless required by applicable law or agreed to in writing, software
#  |  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#  |  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#  |  License for the specific language governing permissions and limitations
#  |  under the License.
#

# stdlib
import inspect
from typing import Callable, TypeVar, get_type_hints

__all__ = ["delegate_kwargs", "delegates"]

_C = TypeVar("_C", bound="Callable")


[docs]def delegate_kwargs(to: Callable, *except_: str) -> Callable[[_C], _C]: r""" Decorator to replace ``**kwargs`` in function signatures with the parameter names from the delegated function. :param to: The function \*\*kwargs is passed on to. :param \*except\_: Parameter names not to delegate. :raises ValueError: if a non-default argument follows a default argument. """ # noqa: D400 # TODO: return annotation def _f(f: _C) -> _C: to_f, from_f = to, f to_sig = inspect.signature(to_f) from_sig = inspect.signature(from_f) to_annotations = get_type_hints(to_f) from_annotations = get_type_hints(from_f) to_params = {k: v for k, v in to_sig.parameters.items() if k not in except_} from_params = dict(from_sig.parameters) if from_params.pop("kwargs", False): if "kwargs" in from_annotations: del from_annotations["kwargs"] for param in from_params: if param in to_params: del to_params[param] f.__signature__ = from_sig.replace( # type: ignore parameters=[*from_params.values(), *to_params.values()] ) f.__annotations__ = {**to_annotations, **from_annotations} return f return _f
[docs]def delegates(to: Callable) -> Callable[[_C], _C]: r""" Decorator to replace ``*args, **kwargs`` function signatures with the signature of the delegated function. :param to: The function the arguments are passed on to. """ # noqa: D400 def copy_annotations(f): if hasattr(to, "__annotations__"): if hasattr(f, "__annotations__"): return_annotation = f.__annotations__.get("return", inspect.Parameter.empty) f.__annotations__.update(to.__annotations__) if return_annotation is not inspect.Parameter.empty: f.__annotations__["return"] = return_annotation else: f.__annotations__ = to.__annotations__ def _f(f: _C) -> _C: to_sig = inspect.signature(to) from_sig = inspect.signature(f) from_params = dict(from_sig.parameters) if tuple(from_params.keys()) == ("args", "kwargs"): f.__signature__ = to_sig # type: ignore copy_annotations(f) elif tuple(from_params.keys()) == ("self", "args", "kwargs"): f.__signature__ = from_sig.replace( # type: ignore parameters=[from_params["self"], *to_sig.parameters.values()] ) copy_annotations(f) return f return _f