def get_dataclass_params(type_def: Type[JsonSchemaMixin]) -> List[ParameterMeta]: """Analyzes properties of dataclass and returns their metadata. :param type_def: :return: """ ret: List[ParameterMeta] = [] sig = inspect.signature(type_def.__init__) # TODO Will this work for inherited properties? Make a test! There is also dataclasses.fields maybe... # TODO use inspect.signature to get reliable order of parameters for name, ttype in get_type_hints(type_def.__init__).items(): if name == "return": continue if issubclass(ttype, JsonSchemaMixin): pm = ParameterMeta(name=name, type="dataclass") # TODO come-up with plugin for this? pm.children = get_dataclass_params(ttype) else: param_type = plugin_from_type(ttype) assert param_type is not None pm = ParameterMeta(name=name, type=param_type.type_name()) def_val = sig.parameters[name].default if def_val is not inspect.Parameter.empty: pm.default_value = param_type.value_to_json(def_val) # TODO description, ranges, etc. ret.append(pm) return ret
def test_settings() -> None: meta = meta_from_def(TestObjectWithSettings) assert len(meta.settings) == 6 assert meta.settings[0].name == "string" assert not meta.settings[0].children assert meta.settings[0].type == plugin_from_type(str).type_name() assert meta.settings[1].name == "integer" assert not meta.settings[1].children assert meta.settings[1].type == plugin_from_type(int).type_name() assert meta.settings[2].name == "boolean" assert not meta.settings[2].children assert meta.settings[2].type == plugin_from_type(bool).type_name() assert meta.settings[3].name == "double" assert not meta.settings[3].children assert meta.settings[3].type == plugin_from_type(float).type_name() assert meta.settings[4].name == "enum" assert not meta.settings[4].children assert meta.settings[4].type == plugin_from_type(StrEnum).type_name() assert meta.settings[5].name == "nested_settings" assert len(meta.settings[5].children) == 1 assert meta.settings[5].type == "dataclass" # TODO plugin for this? nested = meta.settings[5].children[0] assert nested.name == "boolean" assert not nested.children assert nested.type == plugin_from_type(bool).type_name()
def test_plugin_from_type() -> None: assert plugin_from_type(Image.Image) is ImagePlugin
def test_plugin_from_type() -> None: assert plugin_from_type(TestEnum) is StringEnumPlugin
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 test_plugin_from_type() -> None: assert plugin_from_type(Pose) is PosePlugin
def test_plugin_from_type() -> None: assert plugin_from_type(str) is StringPlugin
def test_plugin_from_type() -> None: assert plugin_from_type(bool) is BooleanPlugin
def test_plugin_from_type() -> None: assert plugin_from_type(ProjectRobotJoints) is JointsPlugin