# -*- coding: utf-8 -*-
"""initialization file for `inspect`."""
__author__ = "Nathaniel Starkman"
##############################################################################
# IMPORTS
# GENERAL
from inspect import * # so can be a drop-in for `inspect`
import inspect
from inspect import (
getfullargspec,
FullArgSpec,
Parameter,
Signature as Signature,
_void,
)
from typing import (
Callable,
Dict,
Union,
Any,
Optional,
)
from typing_extensions import Literal
from collections import namedtuple
##############################################################################
# __ALL__
__all__ = [
"POSITIONAL_ONLY",
"POSITIONAL_OR_KEYWORD",
"VAR_POSITIONAL",
"KEYWORD_ONLY",
"VAR_KEYWORD",
# "_void",
# "_empty",
"_placehold",
"_is_empty",
"_is_void",
"_is_placehold",
"_is_placeholder",
"FullerArgSpec",
"getfullerargspec",
"get_annotations_from_signature",
"get_defaults_from_signature",
"get_kwdefaults_from_signature",
"get_kwonlydefaults_from_signature",
"get_kinds_from_signature",
"modify_parameter",
"replace_with_parameter",
"insert_parameter",
"prepend_parameter",
"append_parameter",
"drop_parameter",
"FullerSignature",
"fuller_signature",
]
# __all__ += (
# inspect.__all__ if hasattr(inspect, "__all__") else list(dir(inspect))
# )
##############################################################################
# PARAMETERS
POSITIONAL_ONLY = Parameter.POSITIONAL_ONLY
POSITIONAL_OR_KEYWORD = Parameter.POSITIONAL_OR_KEYWORD
VAR_POSITIONAL = Parameter.VAR_POSITIONAL
KEYWORD_ONLY = Parameter.KEYWORD_ONLY
VAR_KEYWORD = Parameter.VAR_KEYWORD
# placeholders
_empty = Parameter.empty # TODO: add to RTD
_void = _void # TODO: add to RTD
# types
_typing_tuple_false = Union[tuple, Literal[False]]
FullerArgSpec: namedtuple = namedtuple(
"FullerArgSpec",
[
"args",
"defaultargs",
"argdefaults",
"varargs",
"kwonlyargs",
"kwonlydefaults",
"varkw",
"annotations",
"docstring",
],
)
##############################################################################
# CODE
##############################################################################
##########################################################################
# safe placeholder comparison
# some 3rd party packages have quantities which cannot be directly compared to
# via ``==`` or ``!=`` and will throw an exception. These methods implement
# safe testing against ``_empty``, ``_void``, ``_placehold``
# and the combination.
[docs]def _is_empty(value):
"""Test whether `value`==`_empty`."""
try:
value == _empty
except Exception:
# if it throws an exception, it clearly isn't `_empty`
return False
else:
return value == _empty
# /def
[docs]def _is_void(value):
"""Test whether `value`==`_void`."""
try:
value == _void
except Exception:
# if it throws an exception, it clearly isn't `_void`
return False
else:
return value == _void
# /def
[docs]def _is_placehold(value):
"""Test whether `value`==`_placehold`."""
try:
value == _placehold
except Exception:
# if it throws an exception, it clearly isn't `_placehold`
return False
else:
return value == _placehold
# /def
[docs]def _is_placeholder(value):
"""Test whether `value`==`_placeholder`."""
try:
value == _empty
except Exception:
# if it throws an exception, it clearly isn't `_placeholder`
return False
else:
return (value == _empty) | (value == _void) | (value == _placehold)
# /def
###########################################################################
# getfullerargspec
[docs]def getfullerargspec(func: Callable) -> FullerArgSpec:
"""Separated version of FullerArgSpec.
fullargspec with separation of mandatory and optional arguments
adds *defargs* which corresponds *defaults*
Parameters
----------
func : function
the function to inspect
Returns
-------
FullerArgSpec : namedtuple
args : the mandatory arguments
defargs : arguments with defaults
defaults : dictionary of defaults to `defargs`
varargs : variable arguments (args)
kwonlyargs : key-word only arguments
kwonlydefaults : key-word only argument defaults
varkw : variable key-word arguments (kwargs)
annotations : function annotations
docstring : function docstring
"""
spec: FullArgSpec = getfullargspec(func) # get argspec
if spec.defaults is not None: # separate out argument types
args = spec.args[: -len(spec.defaults)]
defargs = spec.args[-len(spec.defaults) :]
defaults = {k: v for k, v in zip(defargs, spec.defaults)}
else: # nothing to separate
args = spec.args
defargs = None
defaults = None
# build FullerArgSpec
return FullerArgSpec(
args=args,
defaultargs=defargs,
argdefaults=defaults,
varargs=spec.varargs,
kwonlyargs=spec.kwonlyargs,
kwonlydefaults=spec.kwonlydefaults,
varkw=spec.varkw,
annotations=spec.annotations,
docstring=func.__doc__,
)
# /def
###########################################################################
# Signature / ArgSpec Interface
[docs]def get_annotations_from_signature(signature: Signature) -> Dict[str, Any]:
"""Get annotations from Signature object.
Parameters
----------
signature: Signature
the object's signature
Returns
-------
annotations: dict
argument {name: annotation} values
return annotations under key 'return'
Examples
--------
>>> def func(x: 'x annotation') -> 'return annotation':
... pass
>>> sig = Signature.from_callable(func)
>>> get_annotations_from_signature(sig)
{'x': 'x annotation', 'return': 'return annotation'}
"""
annotations: Dict[str, Any] = {
k: v.annotation
for k, v in signature.parameters.items()
if v.annotation != _empty
}
annotations["return"] = signature.return_annotation
return annotations
# /def
[docs]def get_defaults_from_signature(signature: Signature) -> tuple:
"""Get defaults from Signature object.
Parameters
----------
signature: Signature
the object's signature
Returns
-------
defaults: tuple
n-tuple for n defaulted positional parameters
Examples
--------
>>> def func(x=2,):
... pass
>>> FullerSignature.from_callable(func).defaults
(2,)
this does not get the keyword only defaults
>>> def func(x=2,*,k=3):
... pass
>>> FullerSignature.from_callable(func).defaults
(2,)
"""
return tuple(
[
p.default
for p in signature.parameters.values()
if (
(p.kind == POSITIONAL_OR_KEYWORD) & ~_is_empty(p.default)
) # the kind
]
) # only defaulted
# /def
[docs]def get_kwdefaults_from_signature(signature: Signature) -> dict:
"""Get key-word only defaults from Signature object.
Parameters
----------
signature: Signature
the object's signature
Returns
-------
defaults: dict
argument {name: default}
Examples
--------
>>> def func(x=2,*,k=3):
... pass
>>> FullerSignature.from_callable(func).kwdefaults
{'k': 3}
"""
return {
n: p.default
for n, p in signature.parameters.items()
if ((p.kind == KEYWORD_ONLY) and not _is_empty(p.default))
}
# /def
get_kwonlydefaults_from_signature = get_kwdefaults_from_signature
[docs]def get_kinds_from_signature(signature: Signature) -> tuple:
"""Get parameter kinds from Signature object.
Parameters
----------
signature: Signature
the object's signature
Returns
-------
kinds: tuple
POSITIONAL_OR_KEYWORD, VAR_POSITIONAL, KEYWORD_ONLY, VAR_KEYWORD
Examples
--------
>>> def func(x, *args, k=3, **kw):
... pass
>>> kinds = FullerSignature.from_callable(func).kinds
>>> kinds[0]
<_ParameterKind.POSITIONAL_OR_KEYWORD: 1>
>>> kinds[1]
<_ParameterKind.VAR_POSITIONAL: 2>
>>> kinds[2]
<_ParameterKind.KEYWORD_ONLY: 3>
>>> kinds[3]
<_ParameterKind.VAR_KEYWORD: 4>
"""
return tuple([p.kind for p in signature.parameters.values()])
# /def
###########################################################################
# Signature Methods
[docs]def modify_parameter(
sig: Signature,
param: Union[str, int],
name: Union[str, _empty] = _empty,
kind: Any = _empty,
default: Any = _empty,
annotation: Any = _empty,
) -> Signature:
"""Modify a Parameter.
Similar to `.replace,` but more convenient for modifying a single parameter
Parameters are immutable, so will create a new `Signature` object
Parameters
----------
sig: Signature
Signature object
param: int or str
the parameter index (or name) in `self.parameters`
name: str
new parameter name, defaults to old parameter name
**default: None**
kind: type
new parameter kind, defaults to old parameter kind
**default: None**
default: any
new parameter default, defaults to old parameter default
**default: None**
annotation: any
new parameter annotation, defaults to old parameter annotation
**default: None**
Returns
-------
Signature
a new Signature object with the replaced parameter
"""
# identify parameter to modify
if isinstance(param, int):
index = param
else:
index = list(sig.parameters.keys()).index(param)
# get the parameters, and the specific param
params = list(sig.parameters.values())
_param = params[index]
# replacements
name = _param.name if name is _empty else name
kind = _param.kind if kind is _empty else kind
default = _param.default if default is _empty else default
annotation = _param.annotation if annotation is _empty else annotation
# adjust parameter list
params[index] = _param.replace(
name=name, kind=kind, default=default, annotation=annotation
)
return sig.replace(parameters=params)
# /def
[docs]def replace_with_parameter(
sig: Signature, name: Union[int, str], param: Parameter
) -> Signature:
"""Replace a Parameter with another Parameter.
Similar to `.replace,` but more convenient for modifying a single parameter
Parameters are immutable, so will create a new `Signature` object
Parameters
----------
sig: Signature
Signature object
name: int or str
parameter to replace
param: Parameter
new parameter kind, defaults to old parameter kind
**default: None**
Returns
-------
Signature
a new Signature object with the replaced parameter
"""
# identify parameter to replace
if isinstance(name, int): # convert index to name
index = name
name = list(sig.parameters.keys())[name]
else:
index = list(sig.parameters.keys()).index(name)
sig = drop_parameter(sig, name)
sig = insert_parameter(sig, index, param)
return sig
# /def
[docs]def insert_parameter(
sig: Signature, index: int, param: Parameter
) -> Signature:
"""Insert a new Parameter.
Similar to .replace, but more convenient for adding a single parameter
Parameters are immutable, so will create a new Signature object
Parameters
----------
sig: Signature
Signature object
index: int
index into Signature.parameters at which to insert new parameter
param: Parameter
param to insert at index
Returns
-------
Signature:
a new Signature object with the inserted parameter
"""
parameters = list(sig.parameters.values())
parameters.insert(index, param)
return sig.replace(parameters=parameters)
# /def
[docs]def prepend_parameter(sig: Signature, param: Parameter) -> Signature:
"""Insert a new Parameter at the start.
Similar to .replace, but more convenient for adding a single parameter
Parameters are immutable, so will create a new Signature object
Parameters
----------
sig: Signature
Signature object
index: int
index into Signature.parameters at which to insert new parameter
param: Parameter
param to insert at `index`
Returns
-------
Signature: Signature
a new `Signature` object with the inserted `param`
TODO
----
have a `skip_self` option to skip self/cls in class methods.
"""
return insert_parameter(sig, 0, param)
# /def
[docs]def append_parameter(sig: Signature, param: Parameter) -> Signature:
"""Insert a new Parameter at the end.
Similar to .replace, but more convenient for adding a single parameter
Parameters are immutable, so will create a new Signature object
Parameters
----------
sig: Signature
Signature object
index: int
index into Signature.parameters at which to insert new parameter
param: Parameter
param to insert at `index`
Returns
-------
Signature: Signature
a new `Signature` object with the inserted `param`
"""
kinds = get_kinds_from_signature(sig)
return insert_parameter(sig, len(kinds) + 1, param)
# /def
[docs]def drop_parameter(
sig: Signature, param: Union[str, int, Parameter]
) -> Signature:
"""Drop a Parameter.
Parameters
----------
sig : Signature
Signature object
param: str, int, Parameter
the parameter to drop in self.parameters
identified by either the name or index
(Parameter type calls name)
Returns
-------
Signature:
a new Signature object with the replaced parameter
"""
if isinstance(param, int): # convert index to name
index = param
elif isinstance(param, str):
index = list(sig.parameters.keys()).index(param)
elif isinstance(param, Parameter):
index = list(sig.parameters.keys()).index(param.name)
else:
raise TypeError
# setup
parameters = list(sig.parameters.values())
# drop
del parameters[index]
return sig.replace(parameters=parameters)
# /def
###########################################################################
# Signature
[docs]class FullerSignature(Signature):
"""Signature with better ArgSpec compatibility.
Though `Signature` is the new object, python still largely
uses the outputs as defined by ``getfullargspec``
This serves as a bridge, providing methods that return
the same output as ``getfullargspec``
"""
@classmethod
def from_callable(cls, obj, *, follow_wrapped=True):
"""From callable.
Parameters
----------
obj : Callable
follow_wrapped : bool
Returns
-------
FullerSignature
"""
sig = super().from_callable(obj, follow_wrapped=follow_wrapped)
sig = FullerSignature(
parameters=sig.parameters.values(),
return_annotation=sig.return_annotation,
)
return sig
# /def
# ------------------------------------------
@property
def __signature__(self) -> Signature:
"""Return a classical Signature."""
return Signature(
parameters=list(self.parameters.values()),
return_annotation=self._return_annotation,
__validate_parameters__=False,
)
# /def
@property
def signature(self) -> Signature:
"""Return a classical Signature."""
return self.__signature__
# /def
@property
def __annotations__(self) -> dict:
"""Get annotations from Signature object.
Returns
-------
annotations: dict
argument {name: annotation} values
return annotations under key 'return'
Examples
--------
>>> def func(x: 'x annotation') -> 'return annotation':
... pass
>>> FullerSignature.from_callable(func).annotations
{'x': 'x annotation', 'return': 'return annotation'}
"""
# res = {
# k: v.annotation
# for k, v in self.parameters.items()
# if v.annotation != _empty
# }
# if self.return_annotation is not _empty:
# res["return"] = self.return_annotation
return get_annotations_from_signature(self.signature)
# /def
@property
def annotations(self) -> Any:
"""Get annotations from Signature object.
Returns
-------
annotations: dict
argument {name: annotation} values
return annotations under key 'return'
Examples
--------
>>> def func(x: 'x annotation') -> 'return annotation':
... pass
>>> FullerSignature.from_callable(func).annotations
{'x': 'x annotation', 'return': 'return annotation'}
"""
return self.__annotations__
# /def
@property
def __defaults__(self) -> Optional[tuple]:
"""Get defaults.
Returns
-------
tuple
n-tuple for n defaulted positional parameters
Examples
--------
>>> def func(x=2,):
... pass
>>> FullerSignature.from_callable(func).defaults
(2,)
"""
# p: Parameter
# out: tuple = tuple(
# [
# p.default
# for p in self.parameters.values()
# if ((p.kind == POSITIONAL_OR_KEYWORD) & _is_empty(p.default))
# ]
# )
# if out == (): # empty list
# return None
# else:
# return out
return get_defaults_from_signature(self.signature)
# /def
@property
def defaults(self) -> Optional[tuple]:
"""Get defaults.
Returns
-------
tuple
n-tuple for n defaulted positional parameters
Examples
--------
>>> def func(x=2,):
... pass
>>> FullerSignature.from_callable(func).defaults
(2,)
"""
return self.__defaults__
# /def
@property
def __kwdefaults__(self) -> Optional[dict]:
"""Get key-word only defaults.
Returns
-------
defaults: dict
argument {name: default value}
Examples
--------
>>> def func(x=2,*,k=3):
... pass
>>> FullerSignature.from_callable(func).kwdefaults
{'k': 3}
"""
# n: str
# p: Parameter
# out: dict = {
# n: p.default
# for n, p in self.parameters.items()
# if ((p.kind == KEYWORD_ONLY) and not _is_empty(p.default))
# }
# if out == {}: # empty dict
# return None
# else:
# return out
return get_kwdefaults_from_signature(self.signature)
# /def
@property
def kwdefaults(self) -> Optional[dict]:
"""Get key-word only defaults.
Returns
-------
defaults: dict
argument {name: default}
Examples
--------
>>> def func(x=2,*,k=3):
... pass
>>> FullerSignature.from_callable(func).kwdefaults
{'k': 3}
"""
return self.__kwdefaults__
# /def
@property
def kwonlydefaults(self) -> Optional[dict]:
"""Get key-word only defaults.
Returns
-------
defaults: dict
argument {name: default}
Examples
--------
>>> def func(x=2,*,k=3):
... pass
>>> FullerSignature.from_callable(func).kwdefaults
{'k': 3}
"""
return self.__kwdefaults__
# /def
@property
def kinds(self) -> tuple:
"""Get parameter kinds.
Returns
-------
kinds: tuple
POSITIONAL_OR_KEYWORD, VAR_POSITIONAL, KEYWORD_ONLY, VAR_KEYWORD
Examples
--------
>>> def func(x, *args, k=3, **kw):
... pass
>>> kinds = FullerSignature.from_callable(func).kinds
>>> kinds[0]
<_ParameterKind.POSITIONAL_OR_KEYWORD: 1>
>>> kinds[1]
<_ParameterKind.VAR_POSITIONAL: 2>
>>> kinds[2]
<_ParameterKind.KEYWORD_ONLY: 3>
>>> kinds[3]
<_ParameterKind.VAR_KEYWORD: 4>
"""
# return tuple([p.kind for p in self.parameters.values()])
return get_kinds_from_signature(self.signature)
@property
def names(self) -> tuple:
"""Get parameter kinds.
Returns
-------
names: tuple of str
Examples
--------
>>> def func(x, *args, k=3, **kw):
... pass
>>> FullerSignature.from_callable(func).names
('x', 'args', 'k', 'kw')
"""
return tuple(self.parameters.keys())
# /def
# (args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, annotations).
# 'args' is a list of the parameter names.
# 'varargs' and 'varkw' are the names of the * and ** parameters or None.
# 'defaults' is an n-tuple of the default values of the last n parameters.
# 'kwonlyargs' is a list of keyword-only parameter names.
# 'kwonlydefaults' is a dictionary mapping names from kwonlyargs to defaults.
# 'annotations' is a dictionary mapping parameter names to annotations.
# def fullargspec(self):
# return
# ------------------------------------------
@property
def index_positional(self) -> _typing_tuple_false:
"""Index(ices) of positional arguments.
This includes defaulted positional arguments.
Returns
-------
tuple or False
False if no positional arguments, tuple of indices otherwise
"""
kinds: tuple = self.kinds
try:
kinds.index(1) # POSITIONAL_OR_KEYWORD = 1
except ValueError:
return False
else:
return tuple(
[i for i, k in enumerate(kinds) if ((k == 0) | (k == 1))]
)
# /def
@property
def index_positional_only(self) -> _typing_tuple_false:
"""Index(ices) of positional-only arguments.
Returns
-------
tuple or False
False if no positional arguments, tuple of indices otherwise
"""
kinds: tuple = self.kinds
try:
kinds.index(0) # POSITIONAL_ONLY = 0
except ValueError:
return False
else:
return tuple([i for i, k in enumerate(kinds) if (k == 0)])
# /def
@property
def index_positional_defaulted(self) -> _typing_tuple_false:
"""Index(ices) of positional arguments with default values.
FIXME, wrong b/c returns indices of POSITIONAL_OR_KEYWORD arguments
that do not have defaults
Returns
-------
tuple or False
False if no positional arguments, tuple of indices otherwise
"""
kinds: tuple = self.kinds
try:
kinds.index(1) # POSITIONAL_OR_KEYWORD = 1
except ValueError:
return False
else:
pos_only: list = self.index_positional_only or []
return tuple(
[
i
for i, k in enumerate(kinds)
if ((k == 1) & (i not in pos_only))
]
)
# return tuple(
# [
# i
# for (k, d) in zip(kinds, defaults)
# if ((k == 1) and not _is_placeholder(d))
# ]
# )
# /def
@property
def index_var_positional(self) -> Union[int, Literal[False]]:
"""Index of `*args`.
Returns
-------
int or False
False if no variable positional argument, index int otherwise
"""
kinds = self.kinds
try:
kinds.index(2) # VAR_POSITIONAL = 2
except ValueError:
return False
else:
return kinds.index(2)
# /def
@property
def index_keyword_only(self) -> _typing_tuple_false:
"""Index of `*args`.
Returns
-------
tuple or False
False if no keyword-only arguments, tuple of indices otherwise
"""
kinds = self.kinds
try:
kinds.index(3) # KEYWORD_ONLY = 3
except ValueError:
return False
else:
return tuple([i for i, k in enumerate(kinds) if (k == 3)])
# /def
@property
def index_end_keyword_only(self) -> int:
"""Index to place new keyword-only parameters.
Returns
-------
int
var_keyword index if var_keyword exists, last index otherwise
"""
index = self.index_var_keyword
if index is False: # no variable kwargs
index = len(self.kinds) + 1
return index
# /def
@property
def index_var_keyword(self) -> Union[int, Literal[False]]:
"""Index of `**kwargs`.
Returns
-------
int or False
False if no variable keyword argument, index int otherwise
"""
kinds = self.kinds
try:
kinds.index(4) # VAR_KEYWORD = 4
except ValueError:
return False
else:
return kinds.index(4)
# /def
# ------------------------------------------
def copy(self) -> Signature:
"""Copy of self."""
return self.replace(parameters=list(self.parameters.values()))
# ------------------------------------------
def modify_parameter(
self,
param: Union[str, int],
name: Union[str, _empty] = _empty,
kind: Any = _empty,
default: Any = _empty,
annotation: Any = _empty,
) -> Signature:
"""Modify a Parameter.
Similar to `.replace,` but more convenient for modifying a single parameter
Parameters are immutable, so will create a new `Signature` object
Parameters
----------
param: int or str
the parameter index (or name) in `self.parameters`
name: str
new parameter name, defaults to old parameter name
**default: None**
kind: type
new parameter kind, defaults to old parameter kind
**default: None**
default: any
new parameter default, defaults to old parameter default
**default: None**
annotation: any
new parameter annotation, defaults to old parameter annotation
**default: None**
Returns
-------
Signature
a new Signature object with the replaced parameter
"""
return modify_parameter(
self,
param=param,
name=name,
kind=kind,
default=default,
annotation=annotation,
)
# /def
def replace_with_parameter(
self, name: Union[int, str], param: Parameter
) -> Any:
"""Replace a Parameter with another Parameter.
Similar to `.replace,` but more convenient for modifying a single parameter
Parameters are immutable, so will create a new `Signature` object
Parameters
----------
name: int or str
parameter to replace
param: Parameter
new parameter kind, defaults to old parameter kind
**default: None**
Returns
-------
Signature
a new Signature object with the replaced parameter
"""
return replace_with_parameter(self, name, param)
# /def
def insert_parameter(self, index: int, parameter: Parameter) -> Signature:
"""Insert a new Parameter.
Similar to .replace, but more convenient for adding a single parameter
Parameters are immutable, so will create a new Signature object
Parameters
----------
index: int
index into Signature.parameters at which to insert new parameter
parameter: Parameter
parameter to insert at `index`
Returns
-------
Signature: Signature
a new `Signature` object with the inserted `parameter`
"""
return insert_parameter(self, index, parameter)
# /def
def prepend_parameter(self, param: Parameter) -> Signature:
"""Insert a new Parameter at the start.
Similar to .replace, but more convenient for adding a single parameter
Parameters are immutable, so will create a new Signature object
Parameters
----------
index: int
index into Signature.parameters at which to insert new parameter
param: Parameter
param to insert at `index`
Returns
-------
Signature: Signature
a new `Signature` object with the inserted `param`
TODO
----
have a `skip_self` option to skip self/cls in class methods.
"""
return prepend_parameter(self, param)
# /def
def append_parameter(self, param: Parameter) -> Signature:
"""Insert a new Parameter at the end.
Similar to .replace, but more convenient for adding a single parameter
Parameters are immutable, so will create a new Signature object
Parameters
----------
index: int
index into Signature.parameters at which to insert new parameter
param: Parameter
param to insert at `index`
Returns
-------
Signature: Signature
a new `Signature` object with the inserted `param`
"""
return append_parameter(self, param)
# /def
def drop_parameter(self, param: str) -> Signature:
"""Drop a Parameter.
Parameters
----------
param: str
the parameter name in self.parameters
Returns
-------
Signature
a new Signature object with the replaced parameter
"""
return drop_parameter(self, param)
# /def
def _default_pos_to_kwonly_from(self, index: int = 0):
"""Promote default positional to keyword only arguments.
Parameter
---------
index: int, optional
default positional arguments after `index`
will be changed to keyword only arguments
``None`` changes all available arguments
example, (x, y=2, z=3) with index 1 means only z is changed
because the defaults are (2, 3)
Returns
-------
Signature
a new Signature object with the promoted parameters
TODO
----
return list/info of parameters promoted
"""
signature: Signature = self
if signature.defaults is None: # no defaults
return signature
# promote parameters
i: int
for i in signature.index_positional_defaulted[index:][::-1]:
signature = signature.modify_parameter(i, kind=KEYWORD_ONLY)
return signature
# /def
def add_var_positional_parameter(
self, name: str = "args", index: Optional[int] = None
):
"""Add var positional parameter.
Does not add if one already exists.
Parameters
----------
name: str
the var_positional argument name
(default 'args')
index: int, optional
the index at which to place the var_positional_parameter
default positional arguments after the var_positional parameter
will be changed to keyword only arguments
Returns
-------
Signature
a new Signature object with the new var_positional parameter
and also promoted parameters if `promote_default_pos`=True
"""
signature: Signature = self
if self.index_var_positional is not False: # already exists
pass
else: # doesn't have ``*``
if index is not None:
# promote default-valued positional arguments to kwargs
if index in self.index_positional_defaulted:
pos_index = self.index_positional_defaulted.index(index)
elif index < self.index_positional_defaulted[0]:
pos_index = None
else:
pos_index = self.index_positional_defaulted[-1] + 1
signature = self._default_pos_to_kwonly_from(index=pos_index)
else: # has no defaulted positionals
if not self.index_positional: # no positional
index = 0
else:
index = self.index_positional[-1] + 1
signature = signature.insert_parameter(
index, Parameter(name, VAR_POSITIONAL),
)
return signature
# /def
def add_var_keyword_parameter(self, name: str = "kwargs"):
"""Add var keyword parameter.
Does not add if one already exists.
Parameters
----------
name: str
the var_keyword argument name
(default 'kwargs')
Returns
-------
Signature
a new Signature object with the new var_positional parameter
and also promoted parameters if `promote_default_pos`=True
"""
if self.index_keyword_only is not False: # already exists
signature = self
else:
signature = self.append_parameter(Parameter(name, VAR_KEYWORD),)
return signature
# /def
# ------------------------------------------------------------------------
[docs]def fuller_signature(obj: Any, *, follow_wrapped: bool = True) -> Any:
"""Get a signature object for the passed callable."""
return FullerSignature.from_callable(obj, follow_wrapped=follow_wrapped)
# /def
##############################################################################
# END