(name: str, *types: T.Union[T.Type, T.Tuple[T.Type, ...]],
                   varargs: T.Optional[T.Union[T.Type, T.Tuple[T.Type, ...]]] = None,
                   optargs: T.Optional[T.List[T.Union[T.Type, T.Tuple[T.Type, ...]]]] = None,
                   min_varargs: int = 0, max_varargs: int = 0)
return inner
Decorator that types type checking of positional arguments.

    This supports two different models of optional arguments, the first is the
    variadic argument model. Variadic arguments are a possibly bounded,
    possibly unbounded number of arguments of the same type (unions are
    supported). The second is the standard default value model, in this case
    a number of optional arguments may be provided, but they are still
    ordered, and they may have different types.

    This function does not support mixing variadic and default arguments.

    :name: The name of the decorated function (as displayed in error messages)
    :varargs: They type(s) of any variadic arguments the function takes. If
        None the function takes no variadic args
    :min_varargs: the minimum number of variadic arguments taken
    :max_varargs: the maximum number of variadic arguments taken. 0 means unlimited
    :optargs: The types of any optional arguments parameters taken. If None
        then no optional parameters are taken.

    Some examples of usage blow:
    >>> @typed_pos_args('mod.func', str, (str, int))
    ... def func(self, state: ModuleState, args: T.Tuple[str, T.Union[str, int]], kwargs: T.Dict[str, T.Any]) -> T.Any:
    ...     pass

    >>> @typed_pos_args('method', str, varargs=str)
    ... def method(self, node: BaseNode, args: T.Tuple[str, T.List[str]], kwargs: T.Dict[str, T.Any]) -> T.Any:
    ...     pass

    >>> @typed_pos_args('method', varargs=str, min_varargs=1)
    ... def method(self, node: BaseNode, args: T.Tuple[T.List[str]], kwargs: T.Dict[str, T.Any]) -> T.Any:
    ...     pass

    >>> @typed_pos_args('method', str, optargs=[(str, int), str])
    ... def method(self, node: BaseNode, args: T.Tuple[str, T.Optional[T.Union[str, int]], T.Optional[str]], kwargs: T.Dict[str, T.Any]) -> T.Any:
    ...     pass

    When should you chose `typed_pos_args('name', varargs=str,
    min_varargs=1)` vs `typed_pos_args('name', str, varargs=str)`?

    The answer has to do with the semantics of the function, if all of the
    inputs are the same type (such as with `files()`) then the former is
    correct, all of the arguments are string names of files. If the first
    argument is something else the it should be separated.
Decorator that types type checking of positional arguments.
Decorator that types type checking of positional arguments .
def typed_pos_args(name: str, *types: T.Union[T.Type, T.Tuple[T.Type, ...]],
                   varargs: T.Optional[T.Union[T.Type, T.Tuple[T.Type, ...]]] = None,
                   optargs: T.Optional[T.List[T.Union[T.Type, T.Tuple[T.Type, ...]]]] = None,
                   min_varargs: int = 0, max_varargs: int = 0) -> T.Callable[..., T.Any]:
    """Decorator that types type checking of positional arguments.

    This supports two different models of optional arguments, the first is the
    variadic argument model. Variadic arguments are a possibly bounded,
    possibly unbounded number of arguments of the same type (unions are
    supported). The second is the standard default value model, in this case
    a number of optional arguments may be provided, but they are still
    ordered, and they may have different types.

    This function does not support mixing variadic and default arguments.

    :name: The name of the decorated function (as displayed in error messages)
    :varargs: They type(s) of any variadic arguments the function takes. If
        None the function takes no variadic args
    :min_varargs: the minimum number of variadic arguments taken
    :max_varargs: the maximum number of variadic arguments taken. 0 means unlimited
    :optargs: The types of any optional arguments parameters taken. If None
        then no optional parameters are taken.

    Some examples of usage blow:
    >>> @typed_pos_args('mod.func', str, (str, int))
    ... def func(self, state: ModuleState, args: T.Tuple[str, T.Union[str, int]], kwargs: T.Dict[str, T.Any]) -> T.Any:
    ...     pass

    >>> @typed_pos_args('method', str, varargs=str)
    ... def method(self, node: BaseNode, args: T.Tuple[str, T.List[str]], kwargs: T.Dict[str, T.Any]) -> T.Any:
    ...     pass

    >>> @typed_pos_args('method', varargs=str, min_varargs=1)
    ... def method(self, node: BaseNode, args: T.Tuple[T.List[str]], kwargs: T.Dict[str, T.Any]) -> T.Any:
    ...     pass

    >>> @typed_pos_args('method', str, optargs=[(str, int), str])
    ... def method(self, node: BaseNode, args: T.Tuple[str, T.Optional[T.Union[str, int]], T.Optional[str]], kwargs: T.Dict[str, T.Any]) -> T.Any:
    ...     pass

    When should you chose `typed_pos_args('name', varargs=str,
    min_varargs=1)` vs `typed_pos_args('name', str, varargs=str)`?

    The answer has to do with the semantics of the function, if all of the
    inputs are the same type (such as with `files()`) then the former is
    correct, all of the arguments are string names of files. If the first
    argument is something else the it should be separated.
    """
    def inner(f: TV_func) -> TV_func:

        @wraps(f)
        def wrapper(*wrapped_args: T.Any, **wrapped_kwargs: T.Any) -> T.Any:
            args = get_callee_args(wrapped_args)[1]

            # These are implementation programming errors, end users should never see them.
            assert isinstance(args, list), args
            assert max_varargs >= 0, 'max_varags cannot be negative'
            assert min_varargs >= 0, 'min_varags cannot be negative'
            assert optargs is None or varargs is None, \
                'varargs and optargs not supported together as this would be ambiguous'

            num_args = len(args)
            num_types = len(types)
            a_types = types

            if varargs:
                min_args = num_types + min_varargs
                max_args = num_types + max_varargs
                if max_varargs == 0 and num_args < min_args:
                    raise InvalidArguments(f'{name} takes at least {min_args} arguments, but got {num_args}.')
                elif max_varargs != 0 and (num_args < min_args or num_args > max_args):
                    raise InvalidArguments(f'{name} takes between {min_args} and {max_args} arguments, but got {num_args}.')
            elif optargs:
                if num_args < num_types:
                    raise InvalidArguments(f'{name} takes at least {num_types} arguments, but got {num_args}.')
                elif num_args > num_types + len(optargs):
                    raise InvalidArguments(f'{name} takes at most {num_types + len(optargs)} arguments, but got {num_args}.')
                # Add the number of positional arguments required
                if num_args > num_types:
                    diff = num_args - num_types
                    a_types = tuple(list(types) + list(optargs[:diff]))
            elif num_args != num_types:
                raise InvalidArguments(f'{name} takes exactly {num_types} arguments, but got {num_args}.')

            for i, (arg, type_) in enumerate(itertools.zip_longest(args, a_types, fillvalue=varargs), start=1):
                if not isinstance(arg, type_):
                    if isinstance(type_, tuple):
                        shouldbe = 'one of: {}'.format(", ".join(f'"{t.__name__}"' for t in type_))
                    else:
                        shouldbe = f'"{type_.__name__}"'
                    raise InvalidArguments(f'{name} argument {i} was of type "{type(arg).__name__}" but should have been {shouldbe}')

            # Ensure that we're actually passing a tuple.
            # Depending on what kind of function we're calling the length of
            # wrapped_args can vary.
            nargs = list(wrapped_args)
            i = nargs.index(args)
            if varargs:
                # if we have varargs we need to split them into a separate
                # tuple, as python's typing doesn't understand tuples with
                # fixed elements and variadic elements, only one or the other.
                # so in that case we need T.Tuple[int, str, float, T.Tuple[str, ...]]
                pos = args[:len(types)]
                var = list(args[len(types):])
                pos.append(var)
                nargs[i] = tuple(pos)
            elif optargs:
                if num_args < num_types + len(optargs):
                    diff = num_types + len(optargs) - num_args
                    nargs[i] = tuple(list(args) + [None] * diff)
                else:
                    nargs[i] = args
            else:
                nargs[i] = tuple(args)
            return f(*nargs, **wrapped_kwargs)

        return T.cast(TV_func, wrapper)
    return inner
def typed_pos_args ( name : str , * types : T . Union [ T . Type , T . Tuple [ T . Type , ... ] ] , varargs : T . Optional [ T . Union [ T . Type , T . Tuple [ T . Type , ... ] ] ] = None , optargs : T . Optional [ T . List [ T . Union [ T . Type , T . Tuple [ T . Type , ... ] ] ] ] = None , min_varargs : int = 0 , max_varargs : int = 0 ) -> T . Callable [ ... , T . Any ] : def inner ( f : TV_func ) -> TV_func : @ wraps ( f ) def wrapper ( * wrapped_args : T . Any , * * wrapped_kwargs : T . Any ) -> T . Any : args = get_callee_args ( wrapped_args ) [ 1 ] # These are implementation programming errors, end users should never see them. assert isinstance ( args , list ) , args assert max_varargs >= 0 , 'max_varags cannot be negative' assert min_varargs >= 0 , 'min_varags cannot be negative' assert optargs is None or varargs is None , 'varargs and optargs not supported together as this would be ambiguous' num_args = len ( args ) num_types = len ( types ) a_types = types if varargs : min_args = num_types + min_varargs max_args = num_types + max_varargs if max_varargs == 0 and num_args < min_args : raise InvalidArguments ( f'{name} takes at least {min_args} arguments, but got {num_args}.' ) elif max_varargs != 0 and ( num_args < min_args or num_args > max_args ) : raise InvalidArguments ( f'{name} takes between {min_args} and {max_args} arguments, but got {num_args}.' ) elif optargs : if num_args < num_types : raise InvalidArguments ( f'{name} takes at least {num_types} arguments, but got {num_args}.' ) elif num_args > num_types + len ( optargs ) : raise InvalidArguments ( f'{name} takes at most {num_types + len(optargs)} arguments, but got {num_args}.' ) # Add the number of positional arguments required if num_args > num_types : diff = num_args - num_types a_types = tuple ( list ( types ) + list ( optargs [ : diff ] ) ) elif num_args != num_types : raise InvalidArguments ( f'{name} takes exactly {num_types} arguments, but got {num_args}.' ) for i , ( arg , type_ ) in enumerate ( itertools . zip_longest ( args , a_types , fillvalue = varargs ) , start = 1 ) : if not isinstance ( arg , type_ ) : if isinstance ( type_ , tuple ) : shouldbe = 'one of: {}' . format ( ", " . join ( f'"{t.__name__}"' for t in type_ ) ) else : shouldbe = f'"{type_.__name__}"' raise InvalidArguments ( f'{name} argument {i} was of type "{type(arg).__name__}" but should have been {shouldbe}' ) # Ensure that we're actually passing a tuple. # Depending on what kind of function we're calling the length of # wrapped_args can vary. nargs = list ( wrapped_args ) i = nargs . index ( args ) if varargs : # if we have varargs we need to split them into a separate # tuple, as python's typing doesn't understand tuples with # fixed elements and variadic elements, only one or the other. # so in that case we need T.Tuple[int, str, float, T.Tuple[str, ...]] pos = args [ : len ( types ) ] var = list ( args [ len ( types ) : ] ) pos . append ( var ) nargs [ i ] = tuple ( pos ) elif optargs : if num_args < num_types + len ( optargs ) : diff = num_types + len ( optargs ) - num_args nargs [ i ] = tuple ( list ( args ) + [ None ] * diff ) else : nargs [ i ] = args else : nargs [ i ] = tuple ( args ) return f ( * nargs , * * wrapped_kwargs ) return T . cast ( TV_func , wrapper ) return inner