def update_signature_return( sig: inspect.Signature, return_type: type = None, yield_type: type = None, existing_annotation_strategy: ExistingAnnotationStrategy = ExistingAnnotationStrategy.REPLICATE, ) -> inspect.Signature: """Update return annotation with the supplied types""" anno = sig.return_annotation if anno is not inspect.Signature.empty: # If generating a stub to apply and there's already a return type # annotation, generate a stub with no return type annotation, to avoid # the possibility of "incompatible annotation" errors. if existing_annotation_strategy == ExistingAnnotationStrategy.OMIT: return sig.replace(return_annotation=inspect.Signature.empty) # Don't change pre-existing annotations unless asked to if existing_annotation_strategy == ExistingAnnotationStrategy.REPLICATE: return sig # NB: We cannot distinguish between functions that explicitly only # return None and those that do so implicitly. In the case of generator # functions both are typed as Iterator[<yield_type>] if (yield_type is not None) and ((return_type is None) or (return_type == NoneType)): anno = make_iterator(yield_type) elif (yield_type is not None) and (return_type is not None): anno = make_generator(yield_type, NoneType, return_type) elif return_type is not None: anno = return_type return sig.replace(return_annotation=anno)
def process_overload_signature(self, overload: Signature) -> Signature: """ Processes the signature of the given overloaded implementation. :param overload: """ parameters = [] non_overload_sig = inspect.signature(self.object) for param, non_overload_param in zip( overload.parameters.values(), non_overload_sig.parameters.values()): default = param.default if default is not Parameter.empty: for check, preprocessor in default_preprocessors: if check(default): default = preprocessor(default) break if param.annotation is non_overload_param.annotation: annotation = Parameter.empty else: annotation = param.annotation parameters.append( param.replace(default=default, annotation=annotation)) if non_overload_sig.return_annotation is overload.return_annotation: overload = overload.replace(parameters=parameters, return_annotation=Parameter.empty) else: overload = overload.replace(parameters=parameters) return overload
def update_signature_args( sig: inspect.Signature, arg_types: Dict[str, type], has_self: bool, existing_annotation_strategy: ExistingAnnotationStrategy = ExistingAnnotationStrategy.REPLICATE, ) -> inspect.Signature: """Update argument annotations with the supplied types""" params = [] for arg_idx, name in enumerate(sig.parameters): param = sig.parameters[name] typ = arg_types.get(name) typ = inspect.Parameter.empty if typ is None else typ is_self = (has_self and arg_idx == 0) annotated = param.annotation is not inspect.Parameter.empty if annotated and existing_annotation_strategy == ExistingAnnotationStrategy.OMIT: # generate no annotation for already-annotated args when generating # a stub to apply, avoiding the possibility of "incompatible # annotation" errors param = param.replace(annotation=inspect.Parameter.empty) # Don't touch existing annotations unless asked to ignore them if not is_self and ( (existing_annotation_strategy == ExistingAnnotationStrategy.IGNORE) or not annotated): param = param.replace(annotation=typ) params.append(param) return sig.replace(parameters=params)
def replace_param(sig: inspect.Signature, param: inspect.Parameter, type_: Type[Any]) -> inspect.Signature: new_param = param.replace(annotation=type_) return sig.replace(parameters=[ new_param if p is param else p for p in sig.parameters.values() ])
def remove_annotation(sig: inspect.Signature): p: inspect.Parameter return sig.replace( parameters=[p.replace(annotation=inspect.Parameter.empty) for p in sig.parameters.values()], return_annotation=inspect.Signature.empty, )
def __init__(self, signature: Signature): """ Arguments: signature: Signature for slots. Important: Only slots that match the signature could be connected to signal. The check is performed only on parameters, and not on return value type (as signals does not have/ignore return values). The match must be exact, including type annotations, parameter names, order, parameter type etc. The sole exception to this rule are excess slot keyword arguments with default values. Note: Signal functions with signatures different from signal could be adapted using `functools.partial`. However, you can "mask" only keyword arguments (without default) and leading positional arguments (as any positional argument binded by name will not mask-out parameter from signature introspection). """ self._sig: Signature = signature.replace(parameters=[p for p in signature.parameters.values() if p.name != 'self'], return_annotation=Signature.empty) #: Toggle to block / unblock signal transmission self.block: bool = False self._slots: List[Callable] = [] self._islots: WeakKeyDictionary = WeakKeyDictionary()
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 modify_parameter( sig: Signature, param: T.Union[str, int], name: T.Union[str, _empty] = _empty, kind: T.Any = _empty, default: T.Any = _empty, annotation: T.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 resolve_annotations(cls_or_callable: Union[Type[object], Callable], sig: inspect.Signature): """Resolve all type-hints in the signature for the class or callable. Parameters ---------- cls_or_callable A class or callable object. sig The signature of the object. Returns ------- A new signature with all annotations resolved, unless a NameError is raised. """ parameters = dict(sig.parameters) to_resolve = {x: y for x, y in parameters.items() if _should_resolve(y)} if to_resolve: # nocover hints = get_type_hints(cls_or_callable) resolved = { x: y.replace(annotation=hints[x]) for x, y in to_resolve.items() } parameters.update(resolved) return sig.replace(parameters=parameters.values()) return sig
def update(sig: Signature, defaults: StrKeyDict) -> Signature: params = [] for param in sig.parameters.values(): try: default = defaults[param.name] params.append(param.replace(default=default)) except (KeyError, ValueError): params.append(param.replace()) return sig.replace(parameters=params)
def _sig_without(sig: inspect.Signature, param: Union[int, str]) -> inspect.Signature: """Removes a parameter from a Signature object If param is an int, remove the parameter at that position, else remove any paramater with that name """ if isinstance(param, int): params = list(sig.parameters.values()) params.pop(param) else: params = [p for name, p in sig.parameters.items() if name != param] return sig.replace(parameters=params)
def update_signature_args(sig: inspect.Signature, arg_types: Dict[str, type], has_self: bool) -> inspect.Signature: """Update argument annotations with the supplied types""" params = [] for arg_idx, name in enumerate(sig.parameters): param = sig.parameters[name] typ = arg_types.get(name) # Don't touch pre-existing annotations and leave self un-annotated if (typ is not None) and \ (param.annotation is inspect.Parameter.empty) and \ ((not has_self) or (arg_idx != 0)): param = param.replace(annotation=typ) params.append(param) return sig.replace(parameters=params)
def process_overload_signature(self, overload: Signature) -> Signature: """ Processes the signature of the given overloaded implementation. :param overload: """ __globals__ = safe_getattr(self.object, "__globals__", {}) overload = evaluate_signature(overload, __globals__) if not inspect.isstaticmethod(self.object, cls=self.parent, name=self.object_name): overload = overload.replace(parameters=list(overload.parameters.values())[1:]) return super().process_overload_signature(overload)
def evaluate_signature(sig: inspect.Signature, globalns: Dict = None, localns: Dict = None) -> inspect.Signature: """Evaluate unresolved type annotations in a signature object.""" def evaluate_forwardref(ref: ForwardRef, globalns: Dict, localns: Dict) -> Any: """Evaluate a forward reference.""" if sys.version_info > (3, 9): return ref._evaluate(globalns, localns, frozenset()) else: return ref._evaluate(globalns, localns) def evaluate(annotation: Any, globalns: Dict, localns: Dict) -> Any: """Evaluate unresolved type annotation.""" try: if isinstance(annotation, str): ref = ForwardRef(annotation, True) annotation = evaluate_forwardref(ref, globalns, localns) if isinstance(annotation, ForwardRef): annotation = evaluate_forwardref(ref, globalns, localns) elif isinstance(annotation, str): # might be a ForwardRef'ed annotation in overloaded functions ref = ForwardRef(annotation, True) annotation = evaluate_forwardref(ref, globalns, localns) except (NameError, TypeError): # failed to evaluate type. skipped. pass return annotation if globalns is None: globalns = {} if localns is None: localns = globalns parameters = list(sig.parameters.values()) for i, param in enumerate(parameters): if param.annotation: annotation = evaluate(param.annotation, globalns, localns) parameters[i] = param.replace(annotation=annotation) return_annotation = sig.return_annotation if return_annotation: return_annotation = evaluate(return_annotation, globalns, localns) return sig.replace(parameters=parameters, return_annotation=return_annotation)
def update_signature_args( sig: inspect.Signature, arg_types: Dict[str, type], has_self: bool, ignore_existing_annotations: bool = False) -> inspect.Signature: """Update argument annotations with the supplied types""" params = [] for arg_idx, name in enumerate(sig.parameters): param = sig.parameters[name] typ = arg_types.get(name) typ = inspect.Parameter.empty if typ is None else typ is_self = (has_self and arg_idx == 0) annotated = param.annotation is not inspect.Parameter.empty # Don't touch existing annotations unless ignore_existing_annotations if not is_self and (ignore_existing_annotations or not annotated): param = param.replace(annotation=typ) params.append(param) return sig.replace(parameters=params)
def update_signature_return(sig: inspect.Signature, return_type: type = None, yield_type: type = None) -> inspect.Signature: """Update return annotation with the supplied types""" anno = sig.return_annotation # Dont' touch pre-existing annotations if anno is not inspect.Signature.empty: return sig # NB: We cannot distinguish between functions that explicitly only # return None and those that do so implicitly. In the case of generator # functions both are typed as Iterator[<yield_type>] if (yield_type is not None) and ((return_type is None) or (return_type == NoneType)): anno = make_iterator(yield_type) elif (yield_type is not None) and (return_type is not None): anno = make_generator(yield_type, NoneType, return_type) elif return_type is not None: anno = return_type return sig.replace(return_annotation=anno)
def test_all_valid_sigs_content(all_valid_signatures_set, make_param_combos): annotations = [None, Parameter.empty] defaults = [None, Parameter.empty] arg1_combos = make_param_combos(['arg1'], Parameter.POSITIONAL_OR_KEYWORD, annotations, defaults) arg2_combos = make_param_combos(['arg2'], Parameter.POSITIONAL_OR_KEYWORD, annotations, defaults) kwarg1_combos = make_param_combos(['kwarg1'], Parameter.KEYWORD_ONLY, annotations, defaults) kwarg2_combos = make_param_combos(['kwarg2'], Parameter.KEYWORD_ONLY, annotations, defaults) defaults = [Parameter.empty] args_combos = make_param_combos(['args'], Parameter.VAR_POSITIONAL, annotations, defaults) kwargs_combos = make_param_combos(['kwargs'], Parameter.VAR_KEYWORD, annotations, defaults) for params in product(arg1_combos, arg2_combos, args_combos, kwarg1_combos, kwarg2_combos, kwargs_combos): params = filter(None, params) try: sig = Signature(params) sig_return = sig.replace(return_annotation=None) except ValueError: # invalid signature continue if (('arg2' in sig.parameters and not 'arg1' in sig.parameters) or ('kwarg2' in sig.parameters and not 'kwarg1' in sig.parameters)): assert sig not in all_valid_signatures_set assert sig_return not in all_valid_signatures_set continue assert sig in all_valid_signatures_set all_valid_signatures_set.remove(sig) assert sig_return in all_valid_signatures_set all_valid_signatures_set.remove(sig_return) assert len(all_valid_signatures_set) == 0
def drop_parameter(sig: Signature, param: T.Union[str, int, Parameter, None]) -> 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 (str) or index (int) (Parameter type calls name) If None, does not drop anything Returns ------- Signature: a new Signature object with the replaced parameter """ if param is None: return sig elif 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 __update_signature_from_docs(sig: inspect.Signature, docs: list) -> inspect.Signature: """:type docs: list[str]""" rtype = next((l[8:] for l in docs if l.startswith(':rtype')), None) ptypes = {t[0]: t[1] for t in [p.split(': ') for p in [l[6:] for l in docs if l.startswith(':type')]]} parameters = [p.replace(annotation=ptypes.get(n, p.annotation)) for n, p in sig.parameters.items()] return sig.replace(parameters=parameters, return_annotation=rtype if rtype else sig.return_annotation)
def _insert_param(sig: Signature, param: Parameter, index=0) -> Signature: params = list(sig.parameters.values()) params.insert(index, param) return sig.replace(parameters=params)
def normalize_signature(sig: inspect.Signature): """Return normalized signature with bare type classes instantiated""" new_params = [normalize_parameter(p) for p in sig.parameters.values()] new_return = normalize_type(sig.return_annotation) return sig.replace(parameters=new_params, return_annotation=new_return)