from typing import Callable
from typing import Any
import inspect
import functools


def partial_kwargs(function: Callable[..., Any], **kwargs: Any) -> Callable[..., Any]:
    """Return a partial function application by overriding default keywords.
    This function is equivalent to `functools.partial(function, **kwargs)` but
    will raise a `ValueError` when called if either the given keyword arguments
    are not defined by `function` or if they do not have defaults.
    This is useful as a way to define a factory function with default parameters
    and then to override them in a safe way.
    Args:
      function: the base function before partial application.
      **kwargs: keyword argument overrides.
    Returns:
      A function.
    """
    # Try to get the argspec of our function which we'll use to get which keywords
    # have defaults.
    argspec = inspect.getfullargspec(function)

    # Figure out which keywords have defaults.
    if argspec.defaults is None:
        defaults = []
    else:
        defaults = argspec.args[-len(argspec.defaults) :]

    # Find any keys not given as defaults by the function.
    unknown_kwargs = set(kwargs.keys()).difference(defaults)

    # Raise an error
    if unknown_kwargs:
        error_string = "Cannot override unknown or non-default kwargs: {}"
        raise ValueError(error_string.format(", ".join(unknown_kwargs)))

    return functools.partial(function, **kwargs)