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``` :return: 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_with_inline_types(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) gen_ast = emit.function( intermediate_repr=parse.function( function_def=function_def, function_name=function_name, function_type=function_type, ), function_name=function_name, function_type=function_type, emit_default_doc=False, inline_types=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_emit_as_kwonlyargs(self) -> None: """ Tests whether `function` produces method with keyword only arguments """ function_def = deepcopy( next( filter( rpartial(isinstance, FunctionDef), ast.parse(class_with_method_types_str.replace("self", "self, *")) .body[0] .body, ) ) ) # Reindent docstring function_def.body[0].value.value = "\n{tab}{docstring}\n{tab}".format( tab=tab, docstring=reindent( deindent(ast.get_docstring(function_def)), ), ) 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, inline_types=True, emit_separating_tab=PY3_8, indent_level=0, emit_as_kwonlyargs=True, ) gen_ast.body[0].value.value = "\n{tab}{docstring}".format( tab=tab, docstring=reindent(deindent(ast.get_docstring(gen_ast))) ) 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 = deepcopy( next( filter( rpartial(isinstance, FunctionDef), class_with_method_types_ast.body ) ) ) # Reindent docstring function_def.body[0].value.value = "\n{tab}{docstring}\n{tab}".format( tab=tab, docstring=reindent( deindent(ast.get_docstring(function_def).translate("\n\t")), ), ) 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, inline_types=True, emit_separating_tab=PY3_8, indent_level=0, emit_as_kwonlyargs=False, ) gen_ast.body[0].value.value = "\n{tab}{docstring}".format( tab=tab, docstring=reindent(deindent(ast.get_docstring(gen_ast))) ) 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 = deepcopy( next(filter(rpartial(isinstance, FunctionDef), class_with_method_ast.body)) ) # Reindent docstring function_def.body[0].value.value = "\n{tab}{docstring}\n{tab}".format( tab=tab, docstring=reindent(ast.get_docstring(function_def)) ) ir = parse.function(function_def) gen_ast = emit.function( ir, function_name=function_def.name, function_type=get_function_type(function_def), emit_default_doc=False, inline_types=False, indent_level=1, emit_separating_tab=True, emit_as_kwonlyargs=False, ) gen_ast.body[0].value.value = "\n{tab}{docstring}\n{tab}".format( tab=tab, docstring=reindent(ast.get_docstring(gen_ast)) ) 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 `{!r}`".format( 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", } # if "return_type" in intermediate_repr.get("returns", {}): # pp({'intermediate_repr["returns"]["return_type"]': intermediate_repr["returns"]["return_type"]}) # intermediate_repr["returns"]["return_type"].update = dict( # interpolate_defaults(intermediate_repr["returns"]["return_type"]) # ) 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 `{!r}`".format( type(function_def).__name__) assert (function_name is None or function_def.name == function_name), "Expected {!r} got {!r}".format( function_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 # if "typ" not in _param: # _param["typ"] = ( # "Optional[dict]" # if function_arguments.kwarg.annotation is None # else to_code(function_arguments.kwarg.annotation).rstrip("\n") # ) 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
def function(function_def, infer_type=False, 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 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``` :return: 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) parsed_source = ast.parse(getsource(function_def).lstrip()).body[0] ir["_internal"] = { "body": list( filterfalse(rpartial(isinstance, AnnAssign), parsed_source.body) ), "from_name": parsed_source.name, "from_type": "cls", } return ir assert isinstance( function_def, FunctionDef ), "Expected 'FunctionDef' got `{!r}`".format(type(function_def).__name__) assert ( function_name is None or function_def.name == function_name ), "Expected {!r} got {!r}".format(function_name, function_def.name) found_type = get_function_type(function_def) # Read docstring intermediate_repr_docstring = ( get_docstring(function_def) if isinstance(function_def, FunctionDef) else None ) if intermediate_repr_docstring is None: intermediate_repr = { "name": function_name or function_def.name, "params": OrderedDict( map( func_arg2param, function_def.args.args if found_type == "static" else function_def.args.args[1:], ) ), "returns": None, } else: intermediate_repr = docstring( intermediate_repr_docstring.replace(":cvar", ":param"), infer_type=infer_type, ) intermediate_repr.update( { "name": function_name or function_def.name, "type": function_type or found_type, } ) if function_def.body: intermediate_repr["_internal"] = { "body": function_def.body if intermediate_repr_docstring is None else function_def.body[1:], "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 # if "typ" not in _param: # _param["typ"] = ( # "Optional[dict]" # if function_def.args.kwarg.annotation is None # else to_code(function_def.args.kwarg.annotation).rstrip("\n") # ) params_to_append[function_def.args.kwarg.arg] = _param del _param idx = count() # Set defaults if intermediate_repr["params"]: deque( map( lambda args_defaults: deque( map( lambda idxparam_idx_arg: intermediate_repr["params"][ idxparam_idx_arg[2].arg ].update( dict( ( {} if getattr(idxparam_idx_arg[2], "annotation", None) is None else dict( typ=to_code( idxparam_idx_arg[2].annotation ).rstrip("\n") ) ), **( lambda _defaults: dict( default=( lambda v: ( _defaults[idxparam_idx_arg[1]] if isinstance( _defaults[idxparam_idx_arg[1]], (NameConstant, Constant), ) else v ) if v is None else v )(get_value(_defaults[idxparam_idx_arg[1]])) ) if idxparam_idx_arg[1] < len(_defaults) and _defaults[idxparam_idx_arg[1]] is not None else {} )(getattr(function_def.args, args_defaults[1])), ) ), ( lambda _args: map( lambda idx_arg: (next(idx), idx_arg[0], idx_arg[1]), enumerate( filterfalse( lambda _arg: _arg.arg[0] == "*", ( _args if found_type == "static" or args_defaults[0] == "kwonlyargs" else _args[1:] ), ) ), ) )(getattr(function_def.args, args_defaults[0])), ), maxlen=0, ), (("args", "defaults"), ("kwonlyargs", "kw_defaults")), ), maxlen=0, ) intermediate_repr["params"].update(params_to_append) intermediate_repr["params"] = OrderedDict( map( partial(_set_name_and_type, infer_type=infer_type), 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), intermediate_repr["returns"].items(), ) ) return intermediate_repr