Beispiel #1
0
def test_class_docstring() -> None:

    d = docstring.parse_docstring(TestType.__doc__)

    assert d.short_description == "Short description."
    assert d.returns is None
    assert d.params is None
Beispiel #2
0
def docstrings(type_def: type[Generic]) -> None:
    """Make sure that all actions are properly documented."""

    ignored_parameters = {"self", "an"}

    for _, method in iterate_over_actions(type_def):

        d = parse_docstring(method.__doc__)
        sig = inspect.signature(method)

        assert d.short_description
        sentence_like(d.short_description)

        param_names = sig.parameters.keys() - ignored_parameters

        if not param_names:
            assert d.params is None
            continue

        assert d.params
        assert d.params.keys() == param_names

        for param_name in param_names:
            assert param_name in d.params
            sentence_like(d.params[param_name])
Beispiel #3
0
def test_method_docstring() -> None:

    d = docstring.parse_docstring(TestType.method.__doc__)

    assert d.short_description == "Method."
    assert d.returns is None
    assert d.params is not None
    assert d.params["param1"] == "p1"
    assert d.params["param2"] == "p2"
    assert d.params["param3"] == "p3"
Beispiel #4
0
def object_actions(type_def: Type[Generic], tree: AST) -> Dict[str, ObjectAction]:

    ret: Dict[str, ObjectAction] = {}

    # ...inspect.ismethod does not work on un-initialized classes
    for method_name, method_def in iterate_over_actions(type_def):

        meta: ActionMetadata = method_def.__action__  # type: ignore

        data = ObjectAction(name=method_name, meta=meta)

        if method_name in type_def.CANCEL_MAPPING:
            meta.cancellable = True

        try:

            if not method_def.__doc__:
                doc = {}
            else:
                doc = parse_docstring(method_def.__doc__)
                doc_short = doc["short_description"]
                if doc_short:
                    data.description = doc_short

            signature = inspect.signature(method_def)  # sig.parameters is OrderedDict

            try:
                method_tree = find_function(method_name, tree)
            except SourceException:
                # function is probably defined in predecessor, will be added later
                continue

            hints = get_type_hints(method_def)  # standard (unordered) dict

            if "an" not in signature.parameters.keys():
                raise IgnoreActionException("Action is missing 'an' parameter.")

            try:
                if hints["an"] != Optional[str]:
                    raise IgnoreActionException("Parameter 'an' has invalid type annotation.")
            except KeyError:
                raise IgnoreActionException("Parameter 'an' is missing type annotation.")

            parameter_names_without_self = list(signature.parameters.keys())[1:]

            if not parameter_names_without_self or parameter_names_without_self[-1] != "an":
                raise IgnoreActionException("The 'an' parameter have to be the last one.")

            # handle return
            try:
                return_ttype = hints["return"]
            except KeyError:
                raise IgnoreActionException("Action is missing return type annotation.")

            # ...just ignore NoneType for returns
            if return_ttype != type(None):  # noqa: E721

                if typing_inspect.is_tuple_type(return_ttype):
                    for arg in typing_inspect.get_args(return_ttype):
                        resolved_param = plugin_from_type(arg)
                        if resolved_param is None:
                            raise IgnoreActionException("None in return tuple is not supported.")
                        data.returns.append(resolved_param.type_name())
                else:
                    # TODO resolving needed for e.g. enums - add possible values to action metadata somewhere?
                    data.returns = [plugin_from_type(return_ttype).type_name()]

            for name in parameter_names_without_self[:-1]:  # omit also an

                try:
                    ttype = hints[name]
                except KeyError:
                    raise IgnoreActionException(f"Parameter {name} is missing type annotation.")

                param_type = plugin_from_type(ttype)

                assert param_type is not None

                args = ParameterMeta(name=name, type=param_type.type_name())
                try:
                    param_type.meta(args, method_def, method_tree)
                except ParameterPluginException as e:
                    raise IgnoreActionException(e) from e

                if name in type_def.DYNAMIC_PARAMS:
                    args.dynamic_value = True
                    dvp = type_def.DYNAMIC_PARAMS[name][1]
                    if dvp:
                        args.dynamic_value_parents = dvp

                def_val = signature.parameters[name].default
                if def_val is not inspect.Parameter.empty:
                    args.default_value = param_type.value_to_json(def_val)

                try:
                    args.description = doc["params"][name].strip()
                except KeyError:
                    pass

                data.parameters.append(args)

        except Arcor2Exception as e:
            data.disabled = True
            data.problem = str(e)
            glob.logger.warn(f"Disabling action {method_name} of  {type_def.__name__}. {str(e)}")

        ret[data.name] = data

    return ret
Beispiel #5
0
def object_actions(plugins: Dict[Type, Type[ParameterPlugin]], type_def: Union[Type[Generic], Type[Service]],
                   source: str) -> ObjectActions:

    ret: ObjectActions = []
    tree = horast.parse(source)

    # ...inspect.ismethod does not work on un-initialized classes
    for method_name, method_def in inspect.getmembers(type_def, predicate=inspect.isfunction):

        # TODO check also if the method has 'action' decorator (ast needed)
        if not hasattr(method_def, "__action__"):
            continue

        # action from ancestor, will be copied later (only if the action was not overridden)
        base_cls_def = type_def.__bases__[0]
        if hasattr(base_cls_def, method_name) and getattr(base_cls_def, method_name) == method_def:
            continue

        meta: ActionMetadata = method_def.__action__

        data = ObjectAction(name=method_name, meta=meta)

        if method_name in type_def.CANCEL_MAPPING:
            meta.cancellable = True

        doc = parse_docstring(method_def.__doc__)
        doc_short = doc["short_description"]
        if doc_short:
            data.description = doc_short

        signature = inspect.signature(method_def)

        method_tree = find_function(method_name, tree)

        try:
            for name, ttype in get_type_hints(method_def).items():

                try:
                    param_type = plugins[ttype]
                except KeyError:
                    for k, v in plugins.items():
                        if not v.EXACT_TYPE and inspect.isclass(ttype) and issubclass(ttype, k):
                            param_type = v
                            break
                    else:
                        if name == "return":  # noqa: E721
                            # ...just ignore NoneType for returns
                            continue

                        # ignore action with unknown parameter type
                        raise IgnoreActionException(f"Parameter {name} of action {method_name}"
                                                    f" has unknown type {ttype}.")

                if name == "return":
                    data.returns = param_type.type_name()
                    continue

                args = ActionParameterMeta(name=name, type=param_type.type_name())
                try:
                    param_type.meta(args, method_def, method_tree)
                except ParameterPluginException as e:
                    # TODO log exception
                    raise IgnoreActionException(e)

                if name in type_def.DYNAMIC_PARAMS:
                    args.dynamic_value = True
                    dvp = type_def.DYNAMIC_PARAMS[name][1]
                    if dvp:
                        args.dynamic_value_parents = dvp

                def_val = signature.parameters[name].default
                if def_val is not inspect.Parameter.empty:
                    args.default_value = def_val

                try:
                    args.description = doc["params"][name].strip()
                except KeyError:
                    pass

                data.parameters.append(args)

        except IgnoreActionException as e:
            data.disabled = True
            data.problem = str(e)
            # TODO log exception

        ret.append(data)

    return ret
Beispiel #6
0
 def description(
         cls) -> str:  # TODO mixin with common stuff for objects/services?
     return parse_docstring(cls.__doc__)["short_description"]
Beispiel #7
0
 def description(cls) -> str:
     if not cls.__doc__:
         return "No description available."
     return parse_docstring(cls.__doc__)["short_description"]
Beispiel #8
0
 def description(cls) -> str:
     return parse_docstring(cls.__doc__)["short_description"]