Beispiel #1
0
def get_callable_placeholder(function: Callable,
                             owner_class=None,
                             show_module=False) -> CallablePlaceholder:
    isclass = inspect.isclass(function)
    orig_fn = function

    # Get base name.
    name_parts = []
    if show_module:
        name_parts.append(function.__module__)
    if owner_class:
        name_parts.append(owner_class.__name__)
    if hasattr(function, "__name__"):
        name_parts.append(function.__name__)
    else:
        name_parts.append(type(function).__name__)
        name_parts.append("__call__")
        function = function.__call__
    if isclass:
        function = getattr(function, "__init__", None)

    name = ".".join(name_parts)
    sig = inspect.signature(function)
    fn_type: FunctionTypes = get_fn_type(orig_fn)

    params = []
    for p in sig.parameters.values():
        annotation = None
        default_value = None
        if p.annotation is not inspect._empty:  # type: ignore
            annotation = inspect.formatannotation(p.annotation)
        if p.default is not inspect._empty:  # type: ignore
            if isinstance(p.default, str):
                default_value = repr(p.default)
            else:
                default_value = str(p.default)
        if annotation is not None:
            annotation = cleanup_type(annotation)
        params.append(CallableArg(p.name, annotation, default_value))

    return_annotation = None
    if sig.return_annotation is not inspect._empty:  # type: ignore
        return_annotation = inspect.formatannotation(sig.return_annotation)
    if return_annotation is not None:
        return_annotation = cleanup_type(return_annotation)
    return CallablePlaceholder(
        simple=str(sig),
        name=name,
        args=params,
        return_type=return_annotation,
        fn_type=fn_type,
    )
Beispiel #2
0
    def __str__(self):
        """Debug purposes."""
        as_str = self.name

        # POSITIONAL_ONLY is only in precompiled functions
        if self.kind == self.POSITIONAL_ONLY:  # pragma: no cover
            as_str = '' if as_str is None else '<{as_str}>'.format(
                as_str=as_str)

        # Add annotation if applicable (python 3 only)
        if self.annotation is not self.empty:  # pragma: no cover
            as_str += ': {annotation!s}'.format(
                annotation=formatannotation(self.annotation))

        value = self.value
        if self.empty == value:
            if self.VAR_POSITIONAL == self.kind:
                value = ()
            elif self.VAR_KEYWORD == self.kind:
                value = {}

        as_str += '={value!r}'.format(value=value)

        if self.default is not self.empty:
            as_str += '  # {self.default!r}'.format(self=self)

        if self.kind == self.VAR_POSITIONAL:
            as_str = '*' + as_str
        elif self.kind == self.VAR_KEYWORD:
            as_str = '**' + as_str

        return as_str
Beispiel #3
0
    def return_annotation(self):
        """Returns back return type annotation if a valid one is found"""
        signature = self._signature(self.func)
        if not signature or signature.return_annotation == inspect._empty:
            return ""

        return inspect.formatannotation(signature.return_annotation)
Beispiel #4
0
    def __str__(self) -> str:
        components = []
        if self:
            pos_param = next(
                findparam(self, lambda p: p.kind is POSITIONAL_ONLY),
                None,
            )
            has_positional = bool(pos_param)
            vpo_param = get_var_positional_parameter(self)
            has_var_positional = bool(vpo_param)

            for i, param in enumerate(self):
                last_ = self[i - 1] if (i > 0) else None
                next_ = self[i + 1] if (len(self) > i + 1) else None

                if (
                        not has_var_positional and
                        self[i].kind is KEYWORD_ONLY and
                        (not last_ or last_.kind is not KEYWORD_ONLY)
                    ):
                    components.append('*')

                components.append(str(param))
                if (
                        has_positional and
                        self[i].kind is POSITIONAL_ONLY and
                        (not next_ or next_.kind is not POSITIONAL_ONLY)
                    ):
                    components.append('/')

        ra_str = ' -> {}'.format(
            inspect.formatannotation(self.return_annotation)
        ) if self.return_annotation is not empty.native else ''

        return '({}){}'.format(', '.join(components), ra_str)
Beispiel #5
0
    def inspect_formatargspec(
        args,
        varargs=None,
        varkw=None,
        defaults=None,
        kwonlyargs=(),
        kwonlydefaults={},
        annotations={},
        formatarg=str,
        formatvarargs=lambda name: "*" + name,
        formatvarkw=lambda name: "**" + name,
        formatvalue=lambda value: "=" + repr(value),
        formatreturns=lambda text: " -> " + text,
        formatannotation=formatannotation,
    ):
        """Copy formatargspec from python 3.7 standard library.

        Python 3 has deprecated formatargspec and requested that Signature
        be used instead, however this requires a full reimplementation
        of formatargspec() in terms of creating Parameter objects and such.
        Instead of introducing all the object-creation overhead and having
        to reinvent from scratch, just copy their compatibility routine.

        Utimately we would need to rewrite our "decorator" routine completely
        which is not really worth it right now, until all Python 2.x support
        is dropped.

        """
        def formatargandannotation(arg):
            result = formatarg(arg)
            if arg in annotations:
                result += ": " + formatannotation(annotations[arg])
            return result

        specs = []
        if defaults:
            firstdefault = len(args) - len(defaults)
        for i, arg in enumerate(args):
            spec = formatargandannotation(arg)
            if defaults and i >= firstdefault:
                spec = spec + formatvalue(defaults[i - firstdefault])
            specs.append(spec)
        if varargs is not None:
            specs.append(formatvarargs(formatargandannotation(varargs)))
        else:
            if kwonlyargs:
                specs.append("*")
        if kwonlyargs:
            for kwonlyarg in kwonlyargs:
                spec = formatargandannotation(kwonlyarg)
                if kwonlydefaults and kwonlyarg in kwonlydefaults:
                    spec += formatvalue(kwonlydefaults[kwonlyarg])
                specs.append(spec)
        if varkw is not None:
            specs.append(formatvarkw(formatargandannotation(varkw)))
        result = "(" + ", ".join(specs) + ")"
        if "return" in annotations:
            result += formatreturns(formatannotation(annotations["return"]))
        return result
Beispiel #6
0
def formatannotation(annotation: Any) -> str:
    """
    Like `inspect.formatannotation()`, but with a small bugfix for Python 3.9's GenericAlias annotations.
    """
    # a small inconsistency in Python 3.9,
    # formatannotation(list[str]) returns "list".
    if isinstance(annotation, type) and get_args(annotation):
        return repr(annotation)
    return inspect.formatannotation(annotation)
Beispiel #7
0
    def inspect_formatargspec(
        args,
        varargs=None,
        varkw=None,
        defaults=None,
        kwonlyargs=(),
        kwonlydefaults={},
        annotations={},
        formatarg=str,
        formatvarargs=lambda name: "*" + name,
        formatvarkw=lambda name: "**" + name,
        formatvalue=lambda value: "=" + repr(value),
        formatreturns=lambda text: " -> " + text,
        formatannotation=formatannotation,
    ):
        """Copy formatargspec from python 3.7 standard library.

        Python 3 has deprecated formatargspec and requested that Signature
        be used instead, however this requires a full reimplementation
        of formatargspec() in terms of creating Parameter objects and such.
        Instead of introducing all the object-creation overhead and having
        to reinvent from scratch, just copy their compatibility routine.

        """

        def formatargandannotation(arg):
            result = formatarg(arg)
            if arg in annotations:
                result += ": " + formatannotation(annotations[arg])
            return result

        specs = []
        if defaults:
            firstdefault = len(args) - len(defaults)
        for i, arg in enumerate(args):
            spec = formatargandannotation(arg)
            if defaults and i >= firstdefault:
                spec = spec + formatvalue(defaults[i - firstdefault])
            specs.append(spec)
        if varargs is not None:
            specs.append(formatvarargs(formatargandannotation(varargs)))
        else:
            if kwonlyargs:
                specs.append("*")
        if kwonlyargs:
            for kwonlyarg in kwonlyargs:
                spec = formatargandannotation(kwonlyarg)
                if kwonlydefaults and kwonlyarg in kwonlydefaults:
                    spec += formatvalue(kwonlydefaults[kwonlyarg])
                specs.append(spec)
        if varkw is not None:
            specs.append(formatvarkw(formatargandannotation(varkw)))
        result = "(" + ", ".join(specs) + ")"
        if "return" in annotations:
            result += formatreturns(formatannotation(annotations["return"]))
        return result
Beispiel #8
0
 def format_annotation(an):
     if an == inspect.Parameter.empty:
         return ""
     if isinstance(an, types.FunctionType) and (
         an.__code__.co_filename.endswith("typing.py")
         and an.__code__.co_name == "new_type"
     ):
         return an.__name__
     out = inspect.formatannotation(an)
     out = out.replace("conducto.pipeline.", "")
     return out
Beispiel #9
0
def _get_args(func: inspect.Signature) -> Iterable[MethodArgument]:
    found_first = False
    for argname, arg in func.parameters.items():
        if found_first is False and argname in {'self', 'cls'}:
            continue
        if found_first is False:
            found_first = True

        yield MethodArgument(
            name=argname,
            value=inspect.formatannotation(arg.annotation),
        )
Beispiel #10
0
    def inspect_formatargspec(
            args, varargs=None, varkw=None, defaults=None,
            kwonlyargs=(), kwonlydefaults={}, annotations={},
            formatarg=str,
            formatvarargs=lambda name: '*' + name,
            formatvarkw=lambda name: '**' + name,
            formatvalue=lambda value: '=' + repr(value),
            formatreturns=lambda text: ' -> ' + text,
            formatannotation=formatannotation):
        """Copy formatargspec from python 3.7 standard library.

        Python 3 has deprecated formatargspec and requested that Signature
        be used instead, however this requires a full reimplementation
        of formatargspec() in terms of creating Parameter objects and such.
        Instead of introducing all the object-creation overhead and having
        to reinvent from scratch, just copy their compatibility routine.

        Utimately we would need to rewrite our "decorator" routine completely
        which is not really worth it right now, until all Python 2.x support
        is dropped.

        """

        def formatargandannotation(arg):
            result = formatarg(arg)
            if arg in annotations:
                result += ': ' + formatannotation(annotations[arg])
            return result
        specs = []
        if defaults:
            firstdefault = len(args) - len(defaults)
        for i, arg in enumerate(args):
            spec = formatargandannotation(arg)
            if defaults and i >= firstdefault:
                spec = spec + formatvalue(defaults[i - firstdefault])
            specs.append(spec)
        if varargs is not None:
            specs.append(formatvarargs(formatargandannotation(varargs)))
        else:
            if kwonlyargs:
                specs.append('*')
        if kwonlyargs:
            for kwonlyarg in kwonlyargs:
                spec = formatargandannotation(kwonlyarg)
                if kwonlydefaults and kwonlyarg in kwonlydefaults:
                    spec += formatvalue(kwonlydefaults[kwonlyarg])
                specs.append(spec)
        if varkw is not None:
            specs.append(formatvarkw(formatargandannotation(varkw)))
        result = '(' + ', '.join(specs) + ')'
        if 'return' in annotations:
            result += formatreturns(formatannotation(annotations['return']))
        return result
Beispiel #11
0
    def inspect_formatargspec(args,
                              varargs=None,
                              varkw=None,
                              defaults=None,
                              kwonlyargs=(),
                              kwonlydefaults={},
                              annotations={},
                              formatarg=str,
                              formatvarargs=lambda name: '*' + name,
                              formatvarkw=lambda name: '**' + name,
                              formatvalue=lambda value: '=' + repr(value),
                              formatreturns=lambda text: ' -> ' + text,
                              formatannotation=formatannotation):
        """Copy formatargspec from python 3.7 standard library.

        Python 3 has deprecated formatargspec and requested that Signature
        be used instead, however this requires a full reimplementation
        of formatargspec() in terms of creating Parameter objects and such.
        Instead of introducing all the object-creation overhead and having
        to reinvent from scratch, just copy their compatibility routine.

        """
        def formatargandannotation(arg):
            result = formatarg(arg)
            if arg in annotations:
                result += ': ' + formatannotation(annotations[arg])
            return result

        specs = []
        if defaults:
            firstdefault = len(args) - len(defaults)
        for i, arg in enumerate(args):
            spec = formatargandannotation(arg)
            if defaults and i >= firstdefault:
                spec = spec + formatvalue(defaults[i - firstdefault])
            specs.append(spec)
        if varargs is not None:
            specs.append(formatvarargs(formatargandannotation(varargs)))
        else:
            if kwonlyargs:
                specs.append('*')
        if kwonlyargs:
            for kwonlyarg in kwonlyargs:
                spec = formatargandannotation(kwonlyarg)
                if kwonlydefaults and kwonlyarg in kwonlydefaults:
                    spec += formatvalue(kwonlydefaults[kwonlyarg])
                specs.append(spec)
        if varkw is not None:
            specs.append(formatvarkw(formatargandannotation(varkw)))
        result = '(' + ', '.join(specs) + ')'
        if 'return' in annotations:
            result += formatreturns(formatannotation(annotations['return']))
        return result
Beispiel #12
0
    def __str__(self):
        """do not render separators

        commented code is what was taken out from
        the copy/pasted inspect module code :)
        """
        result = []
        # render_pos_only_separator = False
        # render_kw_only_separator = True
        for param in self.parameters.values():
            formatted = str(param)

            # kind = param.kind

            # if kind == inspect._POSITIONAL_ONLY:
            #     render_pos_only_separator = True
            # elif render_pos_only_separator:
            #     # It's not a positional-only parameter, and the flag
            #     # is set to 'True' (there were pos-only params before.)
            #     result.append('/')
            #     render_pos_only_separator = False

            # if kind == inspect._VAR_POSITIONAL:
            #     # OK, we have an '*args'-like parameter, so we won't need
            #     # a '*' to separate keyword-only arguments
            #     render_kw_only_separator = False
            # elif kind == inspect._KEYWORD_ONLY and render_kw_only_separator:
            #     # We have a keyword-only parameter to render and we haven't
            #     # rendered an '*args'-like parameter before, so add a '*'
            #     # separator to the parameters list
            #     # ("foo(arg1, *, arg2)" case)
            #     result.append('*')
            #     # This condition should be only triggered once, so
            #     # reset the flag
            #     render_kw_only_separator = False

            result.append(formatted)

        # if render_pos_only_separator:
        #     # There were only positional-only parameters, hence the
        #     # flag was not reset to 'False'
        #     result.append('/')

        rendered = '({})'.format(', '.join(result))

        if self.return_annotation is not inspect._empty:
            anno = inspect.formatannotation(self.return_annotation)
            rendered += ' -> {}'.format(anno)

        return rendered
Beispiel #13
0
    def _init_restype_fromsignature(self):
        try:
            sig = inspect.signature(self.callable)
        except Exception:
            return

        # If signature has a return annotation, it's in the
        # full signature and we don't need it from here.
        if not sig or sig.return_annotation == inspect._empty:
            return
        ann = inspect.formatannotation(sig.return_annotation)
        if not ann or not self._can_eval(ann):
            return
        return 'return ' + ann
Beispiel #14
0
    def _init_restype_fromsignature(self):
        try:
            sig = inspect.signature(self.callable)
        except Exception:
            return

        # If signature has a return annotation, it's in the
        # full signature and we don't need it from here.
        if not sig or sig.return_annotation == inspect._empty:
            return
        ann = inspect.formatannotation(sig.return_annotation)
        if not ann or not self._can_eval(ann):
            return
        return 'return ' + ann
Beispiel #15
0
def _return_annotation(name, module, obj, link=None):
    try:
        annot = typing.get_type_hints(obj).get('return', '')
    except NameError as e:
        warn("Error handling return annotation for {}: {}".format(name, e.args[0]))
        annot = inspect.signature(inspect.unwrap(obj)).return_annotation
        if annot == inspect.Parameter.empty:
            annot = ''
    if not annot:
        return ''
    s = inspect.formatannotation(annot).replace(' ', '\N{NBSP}')  # Better line breaks
    if link:
        from pdoc.html_helpers import _linkify
        s = re.sub(r'[\w\.]+', partial(_linkify, link=link, module=module), s)
    return s
Beispiel #16
0
def collect(cls):
    values = []
    for name, v in cls._context_members_.items():
        attrname = cls._context_attrs_[name]
        attrdef = getattr(cls, attrname)
        doc = getattr(attrdef, '__doc__')
        if inspect.isfunction(attrdef):
            sig = inspect.signature(attrdef)
            result = inspect.formatannotation(sig.return_annotation)
            sig_good_part = ContextMethod(
                name=name,
                args=list(_get_args(sig)),
                result=result,
                doc=doc,
            )
        elif isinstance(attrdef, property):
            sig = inspect.signature(attrdef.fget)
            sig_txt = inspect.formatannotation(sig.return_annotation)
            sig_good_part = ContextValue(name=name, value=sig_txt, doc=doc)
        else:
            sig_good_part = Unknown(name=name, value=repr(attrdef), doc=doc)
        values.append(sig_good_part)

    return values
Beispiel #17
0
 def return_annotation(self, *, link=None):
     """Formatted function return type annotation or empty string if none."""
     try:
         annot = typing.get_type_hints(self.obj).get('return', '')
     except NameError as e:
         warn("Error handling return annotation for {}: {}".format(
             self.name, e.args[0]))
         annot = inspect.signature(inspect.unwrap(
             self.obj)).return_annotation
         if annot == inspect.Parameter.empty:
             annot = ''
     if not annot:
         return ''
     s = inspect.formatannotation(annot).replace(
         ' ', '\N{NBSP}')  # Better line breaks
     if link:
         from app.pdoc.html_helpers import _linkify
         s = re.sub(r'[\w\.]+',
                    partial(_linkify, link=link, module=self.module), s)
     return s
Beispiel #18
0
    def __str__(self):
        """do not render separators

        commented code is what was taken out from
        the copy/pasted inspect module code :)
        """
        result = []
        # render_pos_only_separator = False
        # render_kw_only_separator = True
        for param in self.parameters.values():
            formatted = str(param)
            result.append(formatted)

        rendered = '({})'.format(', '.join(result))

        if self.return_annotation is not inspect._empty:
            anno = inspect.formatannotation(self.return_annotation)
            rendered += ' -> {}'.format(anno)

        return rendered
Beispiel #19
0
def _render_signature(obj_signature, obj_name):
    """
    This was mostly taken from inspect.Signature.__str__.
    Look there for the comments.
    The only change is to add linebreaks when this gets too long.
    """
    result = []
    pos_only = False
    kw_only = True
    for param in obj_signature.parameters.values():
        if param.kind == inspect._POSITIONAL_ONLY:
            pos_only = True
        elif pos_only:
            result.append('/')
            pos_only = False

        if param.kind == inspect._VAR_POSITIONAL:
            kw_only = False
        elif param.kind == inspect._KEYWORD_ONLY and kw_only:
            result.append('*')
            kw_only = False

        result.append(str(param))

    if pos_only:
        result.append('/')

    # add up name, parameters, braces (2), and commas
    if len(obj_name) + sum(len(r) + 2 for r in result) > 75:
        # This doesn’t fit behind “Signature: ” in an inspect window.
        rendered = '{}(\n{})'.format(obj_name, ''.join(
            '    {},\n'.format(r) for r in result)
        )
    else:
        rendered = '{}({})'.format(obj_name, ', '.join(result))

    if obj_signature.return_annotation is not inspect._empty:
        anno = inspect.formatannotation(obj_signature.return_annotation)
        rendered += ' -> {}'.format(anno)

    return rendered
def _render_signature(obj_signature, obj_name):
    """
    This was mostly taken from inspect.Signature.__str__.
    Look there for the comments.
    The only change is to add linebreaks when this gets too long.
    """
    result = []
    pos_only = False
    kw_only = True
    for param in obj_signature.parameters.values():
        if param.kind == inspect._POSITIONAL_ONLY:
            pos_only = True
        elif pos_only:
            result.append('/')
            pos_only = False

        if param.kind == inspect._VAR_POSITIONAL:
            kw_only = False
        elif param.kind == inspect._KEYWORD_ONLY and kw_only:
            result.append('*')
            kw_only = False

        result.append(str(param))

    if pos_only:
        result.append('/')

    # add up name, parameters, braces (2), and commas
    if len(obj_name) + sum(len(r) + 2 for r in result) > 75:
        # This doesn’t fit behind “Signature: ” in an inspect window.
        rendered = '{}(\n{})'.format(obj_name, ''.join(
            '    {},\n'.format(r) for r in result)
        )
    else:
        rendered = '{}({})'.format(obj_name, ', '.join(result))

    if obj_signature.return_annotation is not inspect._empty:
        anno = inspect.formatannotation(obj_signature.return_annotation)
        rendered += ' -> {}'.format(anno)

    return rendered
Beispiel #21
0
 def formatargandannotation(arg):
     result = formatarg(arg)
     if arg in annotations:
         result += ": " + formatannotation(annotations[arg])
     return result
Beispiel #22
0
 def _annotation_to_str(self, annotation):
     return inspect.formatannotation(annotation)
Beispiel #23
0
    def _params(func_obj, annotate=False, link=None, module=None):
        try:
            signature = inspect.signature(inspect.unwrap(func_obj))
        except ValueError:
            # I guess this is for C builtin functions?
            # TODO: Extract signature from the first line of the docstring, i.e.
            # https://github.com/mitmproxy/pdoc/commit/010d996003bc5b72fcf5fa515edbcc0142819919
            return ["..."]

        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

        params = []
        kw_only = False
        EMPTY = inspect.Parameter.empty

        if link:
            from pdoc.html_helpers import _linkify
            _linkify = partial(_linkify, link=link, module=module)

        for p in signature.parameters.values():  # type: inspect.Parameter
            if not _is_public(p.name) and p.default is not EMPTY:
                continue

            if p.kind == p.VAR_POSITIONAL:
                kw_only = True
            if p.kind == p.KEYWORD_ONLY and not kw_only:
                kw_only = True
                params.append('*')

            p = safe_default_value(p)

            if not annotate:
                p = p.replace(annotation=EMPTY)

            s = str(p)
            if p.annotation is not EMPTY:
                if sys.version_info < (3, 7):
                    # PEP8-normalize whitespace
                    s = re.sub(r'(?<!\s)=(?!\s)', ' = ',
                               re.sub(r':(?!\s)', ': ', s, 1), 1)
                # "Eval" forward-declarations (typing string literals)
                if isinstance(p.annotation, str):
                    s = s.replace("'%s'" % p.annotation, p.annotation, 1)
                s = s.replace(' ',
                              '\N{NBSP}')  # prevent improper line breaking

                if link:
                    annotation = inspect.formatannotation(p.annotation)
                    linked_annotation = re.sub(r'[\w\.]+', _linkify,
                                               annotation)
                    s = linked_annotation.join(s.rsplit(annotation,
                                                        1))  # "rreplace" once

            params.append(s)

        return params
def make_parser(func: Callable,
                add_short_args: bool = True,
                validate_dict_types: bool = False) -> ArgumentParser:
    """
    Automatically configure an argparse parser for `func`.

    To get automatic
    * help strings: write a docstring in the same format as this one (in particular, use
      ":param param_name: help string here").
    * types: use type annotations. The only supported types from `typing` are listed below.
      * `List[type]` and `Sequence[type]` will use `nargs="+", type=type`.
      * `Optional[type]` converts inputs using `type`; a `None` is only possible if this
        is the default value.
      * `Dict[key_type, val_type]` uses `json.loads` or, if the input doesn't parse as
        JSON and it's available, `yaml.safe_load` to parse the input. If the parsed
        value is not a dictionary, an `ArgumentTypeError` is thrown. Tips:
          * quote the dictionary argument on the command line
          * JSON: use double-quotes around all strings (`-p '{"key": "val"}'`)
          * JSON: keys must be strings
          * YAML: put spaces between keys and values (`-p "{key: val}"` NOT `-p "{key:val}"`)
      * `bool` uses `str2bool`; values have to be entered like `--debug True`
    * defaults: just use defaults
    * required params: this is just the parameters with no default values

    All command line arguments are configured to be "keyword only".

    Except for the exceptions noted above, the type annotation will be used directly as
    the type for the parser. This means it must be a callable which accepts a single
    string as input and returns the desired Python object.

    :param func:
    :param add_short_args: if True, "-<short_name>" is used in addition to
      "--<param_name>". The short names are created by taking the first character of
      each _-delimited substring (e.g. "my_arg" -> "ma"). If multiple short names would
      be the same, none are used.
    :param validate_dict_types: if True, `ArgumentTypeError` is thrown if any dictionary
      keys / values aren't of the proper type.
    """
    docstring = func.__doc__ or ""
    match = re.search(r"(.*?)(?=\n\s*:|$)", docstring, re.DOTALL)
    description = re.sub(r"\n\s*", " ",
                         match.group(1)).strip() if match else ""
    parser = ArgumentParser(description=description)

    signature = inspect.signature(func)

    if add_short_args:
        names = signature.parameters.keys()
        short_names = [
            "".join(s[0] for s in name.split("_")) for name in names
        ]
        if len(set(short_names)) != len(short_names):  # name conflicts
            add_short_args = False

    for i, param in enumerate(signature.parameters.values()):
        kwargs = {}
        anno = param.annotation
        origin = getattr(anno, "__origin__", None)
        if origin in (list, Sequence):  # e.g. List[int]
            kwargs["type"] = anno.__args__[0]
            kwargs["nargs"] = "+"
        elif origin == Union:  # Optional[T] is converted to Union[T, None]
            if len(anno.__args__) == 2 and anno.__args__[1] == type(None):
                anno = anno.__args__[0]
                origin = getattr(anno, "__origin__", None)
                if origin in (list, Sequence):
                    kwargs["nargs"] = "+"
                    kwargs["type"] = anno.__args__[0]
                elif origin == dict:
                    key_type, val_type = anno.__args__
                    dict_type = make_dict_type(key_type, val_type,
                                               validate_dict_types)
                    kwargs["type"] = dict_type
                elif anno == bool:
                    kwargs["type"] = str2bool
                else:
                    kwargs["type"] = anno
        elif origin == dict:
            key_type, val_type = anno.__args__
            dict_type = make_dict_type(key_type, val_type, validate_dict_types)
            kwargs["type"] = dict_type
        else:
            if anno is not param.empty:
                if anno == bool:
                    kwargs["type"] = str2bool
                else:
                    kwargs["type"] = anno

        if param.default is not param.empty:
            kwargs["default"] = param.default
        else:
            kwargs["required"] = True

        match = re.search(fr":param {param.name}:(.+?)(?=\n\s*:|$)", docstring,
                          re.DOTALL)
        help_str = re.sub(r"\n\s*", " ",
                          match.group(1)).strip() if match else ""
        annotation_str = inspect.formatannotation(anno)
        if "default" in kwargs:
            help_str += f" [{annotation_str}={kwargs['default']}]"
        else:
            help_str += f" [{annotation_str}]"
        kwargs["help"] = help_str

        if add_short_args:
            parser.add_argument(f"-{short_names[i]}", f"--{param.name}",
                                **kwargs)
        else:
            parser.add_argument(f"--{param.name}", **kwargs)
    return parser
Beispiel #25
0
 def __init__(self, name: str, type: Type):
     self.name = name
     self.type = type
     self.type_show = inspect.formatannotation(type)
Beispiel #26
0
 def formatargandannotation(arg):
     result = formatarg(arg)
     if arg in annotations:
         result += ": " + formatannotation(annotations[arg])
     return result
Beispiel #27
0
def print_docstring(obj, file, depth):
    """Prints a classes's docstring to a file."""

    doc = ClassDoc(obj) if inspect.isclass(obj) else FunctionDoc(obj)

    printf = functools.partial(print, file=file)

    printf(h1(obj.__name__))
    printf(
        linkifier.linkify_fences(paragraph(concat_lines(doc["Summary"])),
                                 depth))
    printf(
        linkifier.linkify_fences(
            paragraph(concat_lines(doc["Extended Summary"])), depth))

    # We infer the type annotations from the signatures, and therefore rely on the signature
    # instead of the docstring for documenting parameters
    try:
        signature = inspect.signature(obj)
    except ValueError:
        signature = (
            inspect.Signature()
        )  # TODO: this is necessary for Cython classes, but it's not correct
    params_desc = {
        param.name: " ".join(param.desc)
        for param in doc["Parameters"]
    }

    # Parameters
    if signature.parameters:
        printf(h2("Parameters"))
    for param in signature.parameters.values():
        # Name
        printf(f"- **{param.name}**", end="")
        # Type annotation
        if param.annotation is not param.empty:
            anno = inspect.formatannotation(param.annotation)
            anno = linkifier.linkify_dotted(anno, depth)
            printf(f" (*{anno}*)", end="")
        # Default value
        if param.default is not param.empty:
            printf(f" – defaults to `{param.default}`", end="")
        printf("\n", file=file)
        # Description
        desc = params_desc[param.name]
        if desc:
            printf(f"    {desc}\n")
    printf("")

    # Attributes
    if doc["Attributes"]:
        printf(h2("Attributes"))
    for attr in doc["Attributes"]:
        # Name
        printf(f"- **{attr.name}**", end="")
        # Type annotation
        if attr.type:
            printf(f" (*{attr.type}*)", end="")
        printf("\n", file=file)
        # Description
        desc = " ".join(attr.desc)
        if desc:
            printf(f"    {desc}\n")
    printf("")

    # Examples
    if doc["Examples"]:
        printf(h2("Examples"))

        in_code = False
        after_space = False

        for line in inspect.cleandoc("\n".join(doc["Examples"])).splitlines():

            if (in_code and after_space and line and not line.startswith(">>>")
                    and not line.startswith("...")):
                printf("```\n")
                in_code = False
                after_space = False

            if not in_code and line.startswith(">>>"):
                printf("```python")
                in_code = True

            after_space = False
            if not line:
                after_space = True

            printf(line)

        if in_code:
            printf("```")
    printf("")

    # Methods
    if inspect.isclass(obj) and doc["Methods"]:
        printf(h2("Methods"))
        printf_indent = lambda x, **kwargs: printf(f"    {x}", **kwargs)

        for meth in doc["Methods"]:

            printf(paragraph(f'???- note "{meth.name}"'))

            # Parse method docstring
            docstring = inherit_docstring(c=obj, meth=meth.name)
            if not docstring:
                continue
            meth_doc = FunctionDoc(func=None, doc=docstring)

            printf_indent(paragraph(" ".join(meth_doc["Summary"])))
            if meth_doc["Extended Summary"]:
                printf_indent(paragraph(" ".join(
                    meth_doc["Extended Summary"])))

            # We infer the type annotations from the signatures, and therefore rely on the signature
            # instead of the docstring for documenting parameters
            signature = inherit_signature(obj, meth.name)
            params_desc = {
                param.name: " ".join(param.desc)
                for param in doc["Parameters"]
            }

            # Parameters
            if len(signature.parameters
                   ) > 1:  # signature is never empty, but self doesn't count
                printf_indent("**Parameters**\n")
            for param in signature.parameters.values():
                if param.name == "self":
                    continue
                # Name
                printf_indent(f"- **{param.name}**", end="")
                # Type annotation
                if param.annotation is not param.empty:
                    printf_indent(
                        f" (*{inspect.formatannotation(param.annotation)}*)",
                        end="")
                # Default value
                if param.default is not param.empty:
                    printf_indent(f" – defaults to `{param.default}`", end="")
                printf_indent("", file=file)
                # Description
                desc = params_desc.get(param.name)
                if desc:
                    printf_indent(f"    {desc}")
            printf_indent("")

            # Returns
            if meth_doc["Returns"]:
                printf_indent("**Returns**\n")
                return_val = meth_doc["Returns"][0]
                if signature.return_annotation is not inspect._empty:
                    if inspect.isclass(signature.return_annotation):
                        printf_indent(
                            f"*{signature.return_annotation.__name__}*: ",
                            end="")
                    else:
                        printf_indent(f"*{signature.return_annotation}*: ",
                                      end="")
                printf_indent(return_val.type)
                printf_indent("")

    # Notes
    if doc["Notes"]:
        printf(h2("Notes"))
        printf(paragraph("\n".join(doc["Notes"])))

    # References
    if doc["References"]:
        printf(h2("References"))
        printf(paragraph("\n".join(doc["References"])))
Beispiel #28
0
def is_annotation_matched(var: object, annotation: object) -> bool:
    """Return True if annotation is matched with the given variable.
    {Any, List, Set, Tuple, Dict, Union, Callable, Iterable, Iterator} from "typing" are supported. 
    
    Parameters:
        var: 
            The variable
        annotation:
            The annotation
    
    Returns:
        True if matched, otherwise False.
    """

    var_type = type(var)
    anno_str = str(annotation).split('[')[0]

    if var is None and annotation is None:
        return True
    elif type(annotation) == type:
        return issubclass(var_type, annotation)
    elif anno_str.startswith('typing.'):
        anno_type = anno_str[7:]
        if anno_type == 'Any':
            return True
        elif anno_type == 'List':
            sub_annotation = annotation.__args__
            if var_type == list:
                if sub_annotation is None:
                    return True
                else:
                    return all(map(lambda x: is_annotation_matched(x, sub_annotation[0]), var))
            else:
                return False
        elif anno_type == 'Set':
            sub_annotation = annotation.__args__
            if var_type == set:
                if sub_annotation is None:
                    return True
                else:
                    return all(map(lambda x: is_annotation_matched(x, sub_annotation[0]), var))
            else:
                return False
        elif anno_type == 'Iterable':
            # currently we can't check the type of items
            return issubclass(var_type, collections.abc.Iterable)
        elif anno_type == 'Iterator':
            # currently we can't check the type of items
            return issubclass(var_type, collections.abc.Iterator)
        elif anno_type == 'Tuple':
            sub_annotation = annotation.__args__
            if var_type == tuple:
                if sub_annotation is None:
                    return True
                if len(sub_annotation) != len(var):
                    return False
                else:
                    return all(map(lambda x, y: is_annotation_matched(x, y), var, sub_annotation))
            else:
                return False
        elif anno_type == 'Dict':
            sub_annotation = annotation.__args__
            if var_type == dict:
                if sub_annotation is None:
                    return True
                else:
                    key_anno, val_anno = sub_annotation
                    return all(map(
                        lambda x: is_annotation_matched(x[0], key_anno) and \
                            is_annotation_matched(x[1], val_anno), var.items()))
            else:
                return False
        elif anno_type == 'Union':
            sub_annotation = annotation.__args__
            if sub_annotation is None:
                return False
            else:
                return any(map(lambda y: is_annotation_matched(var, y), sub_annotation))
        elif anno_type == 'Callable':
            sub_annotation = annotation.__args__
            if callable(var):
                if sub_annotation is None:
                    return True
                else:
                    if type(var).__name__ == 'NestModule':
                        # Nest module
                        # filter out resolved / optional params
                        sig = var.sig
                        func_annos = [v.annotation for k, v in sig.parameters.items() \
                            if not k in var.params.keys() and v.default is inspect.Parameter.empty]
                    else:
                        # regular callable object
                        sig = inspect.signature(var)
                        func_annos = [v.annotation for v in sig.parameters.values()]
                    if len(func_annos) == len(sub_annotation) - 1:
                        return all(map(lambda x, y: x == y, func_annos, sub_annotation)) and \
                            (sub_annotation[-1] == Any or \
                            sub_annotation[-1] == object or \
                            sig.return_annotation == sub_annotation[-1] or
                            (sig.return_annotation is None and sub_annotation[-1] == type(None)))
                    else:
                        return False
            else:
                return False

    raise NotImplementedError('The annotation type %s is not supported' % inspect.formatannotation(annotation))
Beispiel #29
0
 def _annotation_to_str(self, annotation):
     if py_version < 30:
         return ''
     return inspect.formatannotation(annotation)
Beispiel #30
0
def test_formatannotation_still_unfixed():
    """when this tests starts to fail, we can remove the workaround in our formatannotation wrapper"""
    assert formatannotation(list[str]) == "list[str]"
    assert inspect.formatannotation(list[str]) == "list"