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
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])
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"
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
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
def description( cls) -> str: # TODO mixin with common stuff for objects/services? return parse_docstring(cls.__doc__)["short_description"]
def description(cls) -> str: if not cls.__doc__: return "No description available." return parse_docstring(cls.__doc__)["short_description"]
def description(cls) -> str: return parse_docstring(cls.__doc__)["short_description"]