def test_get_function_type(self) -> None: """Test get_function_type returns the right type""" self.assertEqual( get_function_type( FunctionDef( args=arguments( args=[set_arg("something else")], arg=None, ), arguments_args=None, identifier_name=None, stmt=None, ) ), "static", ) self.assertEqual( get_function_type( FunctionDef( args=arguments(args=[], arg=None), arguments_args=None, identifier_name=None, stmt=None, ) ), "static", ) self.assertEqual( get_function_type( FunctionDef( args=arguments( args=[set_arg("self")], arg=None, ), arguments_args=None, identifier_name=None, stmt=None, ) ), "self", ) self.assertEqual( get_function_type( FunctionDef( args=arguments( args=[set_arg("cls")], arg=None, ), arguments_args=None, identifier_name=None, stmt=None, ) ), "cls", )
def _default_options(node, search, type_wanted): """ Conform the given file to the `intermediate_repr` :param node: AST node :type node: ```AST``` :param search: Search query, e.g., ['node_name', 'function_name', 'arg_name'] :type search: ```List[str]``` :param type_wanted: AST instance :type type_wanted: ```AST``` :returns: Arguments to pass to `emit.*` function :rtype: ```Callable[[], dict]``` """ return { "FunctionDef": lambda: { "function_type": node if node is None else get_function_type(node), "function_name": search[-1] if len(search) else "set_cli_args", }, "ClassDef": lambda: { "class_name": search[-1] if len(search) else "ConfigClass", }, }.get(type_wanted.__name__, lambda: {})
def test_to_function_emit_as_kwonlyargs(self) -> None: """ Tests whether `function` produces method with keyword only arguments """ function_def = reindent_docstring( deepcopy( next( filter( rpartial(isinstance, FunctionDef), ast.parse( class_with_method_types_str.replace( "self", "self, *")).body[0].body, )))) function_name = function_def.name function_type = get_function_type(function_def) gen_ast = emit.function( parse.docstring(docstring_str), function_name=function_name, function_type=function_type, emit_default_doc=False, type_annotations=True, emit_separating_tab=True, indent_level=1, emit_as_kwonlyargs=True, ) run_ast_test( self, gen_ast=gen_ast, gold=function_def, )
def test_to_function_with_type_annotations(self) -> None: """ Tests that `function` can generate a function_def with inline types """ function_def = deepcopy( next( filter(rpartial(isinstance, FunctionDef), class_with_method_types_ast.body))) function_name = function_def.name function_type = get_function_type(function_def) reindent_docstring(function_def) gen_ast = emit.function( parse.function( function_def, function_name=function_name, function_type=function_type, ), function_name=function_name, function_type=function_type, emit_default_doc=False, type_annotations=True, emit_separating_tab=True, indent_level=1, emit_as_kwonlyargs=False, ) # emit.file(gen_ast, os.path.join(os.path.dirname(__file__), 'delme.py'), mode='wt') run_ast_test( self, gen_ast=gen_ast, gold=function_def, )
def test_to_function_with_docstring_types(self) -> None: """ Tests that `function` can generate a function_def with types in docstring """ # Sanity check run_ast_test( self, class_with_method_ast, gold=ast.parse(class_with_method_str).body[0], ) function_def = reindent_docstring( deepcopy( next( filter(rpartial(isinstance, FunctionDef), class_with_method_ast.body)))) ir = parse.function(function_def) gen_ast = reindent_docstring( emit.function( ir, function_name=function_def.name, function_type=get_function_type(function_def), emit_default_doc=False, type_annotations=False, indent_level=1, emit_separating_tab=True, emit_as_kwonlyargs=False, word_wrap=False, )) run_ast_test(self, gen_ast=gen_ast, gold=function_def)
def test_to_function(self) -> None: """ Tests whether `function` produces method from `class_with_method_types_ast` given `docstring_str` """ function_def = reindent_docstring( deepcopy( next( filter( rpartial(isinstance, FunctionDef), class_with_method_types_ast.body, )))) function_name = function_def.name function_type = get_function_type(function_def) gen_ast = emit.function( parse.docstring(docstring_str), function_name=function_name, function_type=function_type, emit_default_doc=False, type_annotations=True, emit_separating_tab=True, indent_level=1, emit_as_kwonlyargs=False, ) run_ast_test( self, gen_ast=gen_ast, gold=function_def, )
def argparse_ast(function_def, function_type=None, function_name=None): """ Converts an argparse AST to our IR :param function_def: AST of argparse function_def :type function_def: ```FunctionDef``` :param function_type: Type of function, static is static or global method, others just become first arg :type function_type: ```Literal['self', 'cls', 'static']``` :param function_name: name of function_def :type function_name: ```str``` :returns: a dictionary of form { "name": Optional[str], "type": Optional[str], "doc": Optional[str], "params": OrderedDict[str, {'typ': str, 'doc': Optional[str], 'default': Any}] "returns": Optional[OrderedDict[Literal['return_type'], {'typ': str, 'doc': Optional[str], 'default': Any}),)]] } :rtype: ```dict``` """ assert isinstance( function_def, FunctionDef ), "Expected 'FunctionDef' got `{node_name!r}`".format( node_name=type(function_def).__name__ ) doc_string = get_docstring(function_def) intermediate_repr = { "name": function_name, "type": function_type or get_function_type(function_def), "doc": "", "params": OrderedDict(), } ir = parse_docstring(doc_string, emit_default_doc=True) # Whether a default is required, if not found in doc, infer the proper default from type require_default = False # Parse all relevant nodes from function body body = function_def.body if doc_string is None else function_def.body[1:] for node in body: if is_argparse_add_argument(node): name, _param = parse_out_param( node, emit_default_doc=False, require_default=require_default ) ( intermediate_repr["params"][name].update if name in intermediate_repr["params"] else partial(setitem, intermediate_repr["params"], name) )(_param) if not require_default and _param.get("default") is not None: require_default = True elif isinstance(node, Assign) and is_argparse_description(node): intermediate_repr["doc"] = get_value(node.value) elif isinstance(node, Return) and isinstance(node.value, Tuple): intermediate_repr["returns"] = OrderedDict( ( _parse_return( node, intermediate_repr=ir, function_def=function_def, emit_default_doc=False, ), ) ) inner_body = list( filterfalse( is_argparse_description, filterfalse(is_argparse_add_argument, body), ) ) if inner_body: intermediate_repr["_internal"] = { "body": inner_body, "from_name": function_def.name, "from_type": "static", } return intermediate_repr
def function( function_def, infer_type=False, word_wrap=True, function_type=None, function_name=None, ): """ Converts a method to our IR :param function_def: AST node for function definition :type function_def: ```Union[FunctionDef, FunctionType]``` :param infer_type: Whether to try inferring the typ (from the default) :type infer_type: ```bool``` :param word_wrap: Whether to word-wrap. Set `DOCTRANS_LINE_LENGTH` to configure length. :type word_wrap: ```bool``` :param function_type: Type of function, static is static or global method, others just become first arg :type function_type: ```Literal['self', 'cls', 'static']``` :param function_name: name of function_def :type function_name: ```str``` :returns: a dictionary of form { "name": Optional[str], "type": Optional[str], "doc": Optional[str], "params": OrderedDict[str, {'typ': str, 'doc': Optional[str], 'default': Any}] "returns": Optional[OrderedDict[Literal['return_type'], {'typ': str, 'doc': Optional[str], 'default': Any}),)]] } :rtype: ```dict``` """ if isinstance(function_def, FunctionType): # Dynamic function, i.e., this isn't source code; and is in your memory ir = _inspect(function_def, function_name, word_wrap) parsed_source = ast.parse(getsource(function_def).lstrip()).body[0] body = ( parsed_source.body if ast.get_docstring(parsed_source) is None else parsed_source.body[1:] ) ir["_internal"] = { "body": list(filterfalse(rpartial(isinstance, AnnAssign), body)), "from_name": parsed_source.name, "from_type": "cls", } return ir assert isinstance( function_def, FunctionDef ), "Expected 'FunctionDef' got `{node_name!r}`".format( node_name=type(function_def).__name__ ) assert ( function_name is None or function_def.name == function_name ), "Expected {function_name!r} got {function_def_name!r}".format( function_name=function_name, function_def_name=function_def.name ) found_type = get_function_type(function_def) # Read docstring doc_str = ( get_docstring(function_def) if isinstance(function_def, FunctionDef) else None ) function_def = deepcopy(function_def) function_def.args.args = ( function_def.args.args if found_type == "static" else function_def.args.args[1:] ) if doc_str is None: intermediate_repr = { "name": function_name or function_def.name, "params": OrderedDict(), "returns": None, } else: intermediate_repr = docstring( doc_str.replace(":cvar", ":param"), infer_type=infer_type ) intermediate_repr.update( { "name": function_name or function_def.name, "type": function_type or found_type, } ) function_def.body = function_def.body if doc_str is None else function_def.body[1:] if function_def.body: intermediate_repr["_internal"] = { "body": function_def.body, "from_name": function_def.name, "from_type": found_type, } params_to_append = OrderedDict() if ( hasattr(function_def.args, "kwarg") and function_def.args.kwarg and function_def.args.kwarg.arg in intermediate_repr["params"] ): _param = intermediate_repr["params"].pop(function_def.args.kwarg.arg) assert "typ" in _param _param["default"] = NoneStr params_to_append[function_def.args.kwarg.arg] = _param del _param # Set defaults # Fill with `None`s when no default is given to make the `zip` below it work cleanly for args, defaults in ( ("args", "defaults"), ("kwonlyargs", "kw_defaults"), ): diff = len(getattr(function_def.args, args)) - len( getattr(function_def.args, defaults) ) if diff: setattr( function_def.args, defaults, list(islice(cycle((None,)), 10)) + getattr(function_def.args, defaults), ) ir_merge( intermediate_repr, { "params": OrderedDict( ( func_arg2param( getattr(function_def.args, args)[idx], default=getattr(function_def.args, defaults)[idx], ) for args, defaults in ( ("args", "defaults"), ("kwonlyargs", "kw_defaults"), ) for idx in range(len(getattr(function_def.args, args))) ) ), "returns": None, }, ) intermediate_repr["params"].update(params_to_append) intermediate_repr["params"] = OrderedDict( map( partial(_set_name_and_type, infer_type=infer_type, word_wrap=word_wrap), intermediate_repr["params"].items(), ) ) # Convention - the final top-level `return` is the default intermediate_repr = _interpolate_return(function_def, intermediate_repr) if "return_type" in (intermediate_repr.get("returns") or iter(())): intermediate_repr["returns"] = OrderedDict( map( partial(_set_name_and_type, infer_type=infer_type, word_wrap=word_wrap), intermediate_repr["returns"].items(), ) ) return intermediate_repr