def safe_default_value(p: inspect.Parameter): if p.default is inspect.Parameter.empty: return p replacement = None if p.default is os.environ: replacement = 'os.environ' elif inspect.isclass(p.default): replacement = p.default.__module__ + '.' + p.default.__qualname__ elif ' at 0x' in repr(p.default): replacement = re.sub(r' at 0x\w+', '', repr(p.default)) nonlocal link if link and ('<' in repr(p.default) or '>' in repr(p.default)): import html replacement = html.escape(replacement or p.default) if replacement: class mock: def __repr__(self): return replacement return p.replace(default=mock()) return p
def safe_default_value(p: inspect.Parameter): value = p.default if value is inspect.Parameter.empty: return p replacement = next((i for i in ( 'os.environ', 'sys.stdin', 'sys.stdout', 'sys.stderr', ) if value is eval(i)), None) if not replacement: if isinstance(value, enum.Enum): replacement = str(value) elif inspect.isclass(value): replacement = value.__module__ + '.' + value.__qualname__ elif ' at 0x' in repr(value): replacement = re.sub(r' at 0x\w+', '', repr(value)) nonlocal link if link and ('<' in repr(value) or '>' in repr(value)): import html replacement = html.escape(replacement or repr(value)) if replacement: class mock: def __repr__(self): return replacement return p.replace(default=mock()) return p
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 safe_default_value(p: inspect.Parameter): if p.default is os.environ: class mock: def __repr__(self): return 'os.environ' return p.replace(default=mock()) return p
def signature_from_ast(node: ast.FunctionDef, bound_method: bool, type_comment: ast.FunctionDef) -> Signature: """Return a Signature object for the given *node*. :param bound_method: Specify *node* is a bound method or not """ params = [] if hasattr(node.args, "posonlyargs"): # for py38+ for arg in node.args.posonlyargs: # type: ignore param = Parameter(arg.arg, Parameter.POSITIONAL_ONLY, annotation=arg.type_comment) params.append(param) for arg in node.args.args: param = Parameter(arg.arg, Parameter.POSITIONAL_OR_KEYWORD, annotation=arg.type_comment or Parameter.empty) params.append(param) if node.args.vararg: param = Parameter(node.args.vararg.arg, Parameter.VAR_POSITIONAL, annotation=node.args.vararg.type_comment or Parameter.empty) params.append(param) for arg in node.args.kwonlyargs: param = Parameter(arg.arg, Parameter.KEYWORD_ONLY, annotation=arg.type_comment or Parameter.empty) params.append(param) if node.args.kwarg: param = Parameter(node.args.kwarg.arg, Parameter.VAR_KEYWORD, annotation=node.args.kwarg.type_comment or Parameter.empty) params.append(param) # Remove first parameter when *obj* is bound_method if bound_method and params: params.pop(0) # merge type_comment into signature if not_suppressed(type_comment.argtypes): # type: ignore for i, param in enumerate(params): params[i] = param.replace( annotation=type_comment.argtypes[i]) # type: ignore if node.returns: return Signature(params, return_annotation=node.returns) elif type_comment.returns: return Signature(params, return_annotation=ast_unparse(type_comment.returns)) else: return Signature(params)
def create( cls, param: inspect.Parameter, pdoc: tp.Optional[ParamDocTp], option_generator: FlagsGenerator, ) -> "Argument": hlp = pdoc.doc if pdoc else "" arg = None if isinstance(param.annotation, Argument): arg = param.annotation elif tp_utils.has_annotated(param.annotation): typ, arg = tp_utils.get_annotated_args(param.annotation) param = param.replace(annotation=typ) if arg is None: arg = Argument() arg.kwargs.setdefault("help", hlp) arg.update(param, option_generator) return arg
def normalize_parameter(parameter: Parameter) -> Parameter: if parameter.kind != Parameter.POSITIONAL_OR_KEYWORD: raise IllegalFormatException( f"Invalid signature: name={parameter.name} kind={parameter.kind}") annotation = parameter.annotation if annotation == Parameter.empty: type_hint = str else: type_hint = normalize_type_hint(annotation) # a: int = None -> a: Union[int, None] = None if parameter.default is None and get_origin(type_hint) is not Union: type_hint = Union[type_hint, None] check_parameter_default_type(type_hint, parameter.default) if type_hint == annotation: # Nothing to update return parameter return parameter.replace(annotation=type_hint)
def generate_signature(func: Callable) -> Signature: """Generate a new function signatures with the ``self``, ``invert`` and ``exception`` parameters. Default to :data:`BACK_SIGNATURE` if a functions' signature cannot be read. Examples -------- .. code:: python >>> import inspect >>> func = enumerate # The builtin enumerate function >>> Signature = inspect.Signature # Print the signature of enumerate >>> sgn1: Signature = inspect.signature(func) >>> print(sgn1) (iterable, start=0) # Print the newly create signature >>> sgn2: Signature = generate_signatures(func) >>> print(sgn2) (self, iterable, *args, start=0, invert_: bool = False, exception_: Union[Type[Exception], NoneType] = None, **kwargs) -> None Parameters ---------- func : :data:`Callable<typing.Callable>` A callable object. Returns ------- :class:`Signature<inspect.Signature>` The signature of **func** with the ``self`` and ``invert`` parameters. Return :data:`BACK_SIGNATURE` if funcs' signature cannot be read. """ # noqa try: sgn = signature(func) except ValueError: # Not all callables have a signature which can be read. return BACK_SIGNATURE prm_dict: Dict[_ParameterKind, list] = OrderedDict({ POK: [Parameter(name='self', kind=POK)], VP: [], KO: [], VK: [] }) # Fill the parameter dict for prm in sgn.parameters.values(): if prm.name in ('self', 'cls'): name, _ = _get_cls_annotation(func, prm.name) prm = Parameter(name=name, kind=POK) elif prm.kind is PO: # Positional-only to positional or keyword prm = prm.replace(kind=POK) elif prm.kind is POK and prm.default is not _empty: # keyword or positional to keyword only prm = prm.replace(kind=KO) prm_dict[prm.kind].append(prm) # Double check if the invert and exception parameters are already defined by **func** invert_name = _sanitize_name('invert', func, prm_dict[KO]) exception_name = _sanitize_name('exception', func, prm_dict[KO]) # Ensure the parameter dict contains the following 4 parameters prm_dict[KO].append( Parameter(name=invert_name, kind=KO, default=False, annotation=bool)) prm_dict[KO].append( Parameter(name=exception_name, kind=KO, default=None, annotation=ExType)) if not prm_dict[VP]: prm_dict[VP].append(Parameter(name='args', kind=VP)) if not prm_dict[VK]: prm_dict[VK].append(Parameter(name='kwargs', kind=VK)) # Construct and return a new signature parameters = chain.from_iterable(prm_dict.values()) return Signature(parameters=parameters, return_annotation=None)
# Python sys — System-specific parameters and functions. # This module provides access to some variables used or maintained by the interpreter and to functions that interact # strongly with the interpreter. # inspect — Inspect live objects. # The inspect module provides several useful functions to help get information about live objects such as modules, classes, # methods, functions, tracebacks, frame objects, and code objects. # replace(*[, name][, kind][, default][, annotation]). # Create a new Parameter instance based on the instance replaced was invoked on. To override a Parameter attribute, pass the # corresponding argument. To remove a default value or/and an annotation from a Parameter, pass Parameter.empty. from inspect import Parameter param = Parameter('foo', Parameter.KEYWORD_ONLY, default=42) str(param) # Displays 'foo=42' str(param.replace()) # Will create a shallow copy of 'param' # Displays'foo=42' str(param.replace(default=Parameter.empty, annotation='spam')) # Displays "foo:'spam'"
def replace_parameter( param: inspect.Parameter, converter: Any, callback: Callable[..., Any], original: Parameter, mapping: Dict[str, inspect.Parameter], ) -> inspect.Parameter: try: # If it's a supported annotation (i.e. a transformer) just let it pass as-is. app_commands.transformers.get_supported_annotation(converter) except TypeError: # Fallback to see if the behaviour needs changing origin = getattr(converter, '__origin__', None) args = getattr(converter, '__args__', []) if isinstance(converter, Range): r = converter param = param.replace( annotation=app_commands.Range[r.annotation, r.min, r.max]) # type: ignore elif isinstance(converter, Greedy): # Greedy is "optional" in ext.commands # However, in here, it probably makes sense to make it required. # I'm unsure how to allow the user to choose right now. inner = converter.converter if inner is discord.Attachment: raise TypeError( 'discord.Attachment with Greedy is not supported in hybrid commands' ) param = param.replace( annotation=make_greedy_transformer(inner, original)) elif is_flag(converter): callback.__hybrid_command_flag__ = (param.name, converter) descriptions = {} renames = {} for flag in converter.__commands_flags__.values(): name = flag.attribute flag_param = inspect.Parameter( name=name, kind=param.kind, default=flag.default if flag.default is not MISSING else inspect.Parameter.empty, annotation=flag.annotation, ) pseudo = replace_parameter(flag_param, flag.annotation, callback, original, mapping) if name in mapping: raise TypeError( f'{name!r} flag would shadow a pre-existing parameter') if flag.description is not MISSING: descriptions[name] = flag.description if flag.name != flag.attribute: renames[name] = flag.name mapping[name] = pseudo # Manually call the decorators if descriptions: app_commands.describe(**descriptions)(callback) if renames: app_commands.rename(**renames)(callback) elif is_converter(converter) or converter in CONVERTER_MAPPING: param = param.replace( annotation=make_converter_transformer(converter, original)) elif origin is Union: if len(args) == 2 and args[-1] is _NoneType: # Special case Optional[X] where X is a single type that can optionally be a converter inner = args[0] is_inner_tranformer = is_transformer(inner) if is_converter(inner) and not is_inner_tranformer: param = param.replace( annotation=Optional[make_converter_transformer( inner, original)]) # type: ignore else: raise elif origin: # Unsupported typing.X annotation e.g. typing.Dict, typing.Tuple, typing.List, etc. raise elif callable(converter) and not inspect.isclass(converter): param_count = required_pos_arguments(converter) if param_count != 1: raise param = param.replace( annotation=make_callable_transformer(converter)) return param
def normalize_parameter(p: inspect.Parameter): """Instantiate any ConcreteType classes found in this parameter annotation""" return p.replace(annotation=normalize_type(p.annotation))