예제 #1
0
 def test_annotate_ancestry(self) -> None:
     """ Tests that `annotate_ancestry` properly decorates """
     node = Module(
         body=[
             AnnAssign(
                 annotation=Name(
                     "str",
                     Load(),
                 ),
                 simple=1,
                 target=Name("dataset_name", Store()),
                 value=set_value("~/tensorflow_datasets"),
                 expr=None,
                 expr_target=None,
                 expr_annotation=None,
             ),
             Assign(annotation=None,
                    simple=1,
                    targets=[Name("epochs", Store())],
                    value=set_value("333"),
                    expr=None,
                    expr_target=None,
                    expr_annotation=None,
                    **maybe_type_comment),
         ],
         stmt=None,
     )
     self.assertFalse(hasattr(node.body[0], "_location"))
     self.assertFalse(hasattr(node.body[1], "_location"))
     annotate_ancestry(node)
     self.assertEqual(node.body[0]._location, ["dataset_name"])
     self.assertEqual(node.body[1]._location, ["epochs"])
예제 #2
0
 def test_param2argparse_param_default_list(self) -> None:
     """
     Tests that param2argparse_param works to change the type based on the default
       whence said default is a list
     """
     run_ast_test(
         gen_ast=param2argparse_param(("byo", {
             "default": [],
             "typ": "str"
         }), ),
         gold=Expr(
             Call(
                 args=[set_value("--byo")],
                 func=Attribute(
                     Name("argument_parser", Load()),
                     "add_argument",
                     Load(),
                 ),
                 keywords=[
                     keyword(arg="action",
                             value=set_value("append"),
                             identifier=None),
                     keyword(arg="required",
                             value=set_value(True),
                             identifier=None),
                 ],
                 expr=None,
                 expr_func=None,
             ), ),
         test_case_instance=self,
     )
예제 #3
0
 def test_parse_out_param_fails(self) -> None:
     """ Test that parse_out_param throws NotImplementedError when unsupported type given """
     self.assertRaises(
         NotImplementedError,
         lambda: parse_out_param(
             Expr(
                 Call(
                     args=[set_value("--num")],
                     func=Attribute(
                         Name("argument_parser", Load()),
                         "add_argument",
                         Load(),
                     ),
                     keywords=[
                         keyword(
                             arg="type",
                             value=Subscript(
                                 expr_context_ctx=None,
                                 expr_slice=None,
                                 expr_value=None,
                             ),
                             identifier=None,
                         ),
                         keyword(
                             arg="required",
                             value=set_value(True),
                             identifier=None,
                         ),
                     ],
                     expr=None,
                     expr_func=None,
                 ))),
     )
예제 #4
0
 def test_param2argparse_param_none_default(self) -> None:
     """
     Tests that param2argparse_param works to reparse the default
     """
     run_ast_test(
         gen_ast=param2argparse_param(("yup", {
             "default": NoneStr
         })),
         gold=Expr(
             Call(
                 args=[set_value("--yup")],
                 func=Attribute(
                     Name("argument_parser", Load()),
                     "add_argument",
                     Load(),
                 ),
                 keywords=[
                     keyword(arg="type",
                             value=Name("str", Load()),
                             identifier=None),
                     keyword(arg="default",
                             value=set_value(None),
                             identifier=None),
                 ],
                 expr=None,
                 expr_func=None,
             )),
         test_case_instance=self,
     )
예제 #5
0
    def test_parse_to_scalar(self) -> None:
        """ Test various inputs and outputs for `parse_to_scalar` """
        for fst, snd in (
            (5, 5),
            ("5", "5"),
            (set_value(5), 5),
            (ast.Expr(None), NoneStr),
        ):
            self.assertEqual(parse_to_scalar(fst), snd)

        self.assertEqual(
            get_value(parse_to_scalar(ast.parse("[5]").body[0]).elts[0]), 5
        )
        self.assertTrue(
            cmp_ast(
                parse_to_scalar(ast.parse("[5]").body[0]),
                List([set_value(5)], Load()),
            )
        )

        self.assertEqual(parse_to_scalar(ast.parse("[5]")), "[5]")

        parse_to_scalar(ast.parse("[5]").body[0])

        self.assertRaises(NotImplementedError, parse_to_scalar, memoryview(b""))
        self.assertRaises(NotImplementedError, parse_to_scalar, memoryview(b""))
예제 #6
0
    def test_param2argparse_param_default_function(self) -> None:
        """
        Tests that param2argparse_param works to change the type based on the default
          whence said default is an in-memory function
        """

        function_str = (
            "from operator import add\n"
            "def adder(a, b):\n"
            "{tab}return add(a, b)".format(tab=tab)
        )
        adder = getattr(
            inspectable_compile(function_str),
            "adder",
        )
        pickled_adder = pickle.dumps(adder)  # eww

        run_ast_test(
            gen_ast=param2argparse_param(
                (
                    "byo",
                    {
                        "default": adder,
                        "typ": "str",
                    },
                ),
            ),
            gold=Expr(
                Call(
                    args=[set_value("--byo")],
                    func=Attribute(
                        Name("argument_parser", Load()),
                        "add_argument",
                        Load(),
                    ),
                    keywords=[
                        keyword(
                            arg="type",
                            value=Name("pickle.loads", Load()),
                            identifier=None,
                        ),
                        keyword(
                            arg="default",
                            value=set_value(pickled_adder),
                            identifier=None,
                        ),
                    ],
                    expr=None,
                    expr_func=None,
                )
            ),
            test_case_instance=self,
        )
예제 #7
0
    def test_param2argparse_param_default_torch(self) -> None:
        """
        Tests that param2argparse_param works to change the type based on the default
          whence said default is a proxy for an internal PyTorch type
        """

        class FakeTorch(object):
            """ Not a real torch """

            def __str__(self):
                """But a real str

                :returns: An actual str
                :rtype: ```Literal['<required parameter>']```
                """
                return "<required parameter>"

        # type("FakeTorch", tuple(), {"__str__": lambda _: "<required parameter>"})

        run_ast_test(
            gen_ast=param2argparse_param(
                (
                    "byo",
                    {
                        "default": FakeTorch(),
                    },
                ),
            ),
            gold=Expr(
                Call(
                    args=[set_value("--byo")],
                    func=Attribute(
                        Name("argument_parser", Load()),
                        "add_argument",
                        Load(),
                    ),
                    keywords=[
                        keyword(
                            arg="type",
                            value=Name(FakeTorch.__name__, Load()),
                            identifier=None,
                        ),
                        keyword(arg="required", value=set_value(True), identifier=None),
                    ],
                    expr=None,
                    expr_func=None,
                )
            ),
            test_case_instance=self,
        )
예제 #8
0
    def test_replace_in_ast_with_val_on_non_function(self) -> None:
        """
        Tests that `RewriteAtQuery` can actually replace a node at given location
        """
        parsed_ast = ast_parse(class_str)
        rewrite_at_query = RewriteAtQuery(
            search="ConfigClass.dataset_name".split("."),
            replacement_node=AnnAssign(
                annotation=Name("int", Load()),
                simple=1,
                target=Name("dataset_name", Store()),
                value=set_value(15),
                expr=None,
                expr_target=None,
                expr_annotation=None,
            ),
        )
        gen_ast = rewrite_at_query.visit(parsed_ast)
        self.assertTrue(rewrite_at_query.replaced, True)

        run_ast_test(
            self,
            gen_ast,
            ast.parse(
                class_str.replace('dataset_name: str = "mnist"',
                                  "dataset_name: int = 15")),
        )
예제 #9
0
 def test_emit_ann_assign(self) -> None:
     """ Tests that AnnAssign is emitted from `emit_ann_assign` """
     self.assertIsInstance(class_ast.body[1], AnnAssign)
     self.assertIsInstance(emit_ann_assign(class_ast.body[1]), AnnAssign)
     self.assertIsInstance(emit_ann_assign(class_ast.body[1]), AnnAssign)
     gen_ast = emit_ann_assign(
         find_in_ast(
             "C.function_name.dataset_name".split("."),
             class_with_method_and_body_types_ast,
         ))
     self.assertIsInstance(gen_ast, AnnAssign)
     run_ast_test(
         self,
         gen_ast,
         AnnAssign(
             annotation=Name(
                 "str",
                 Load(),
             ),
             simple=1,
             target=Name("dataset_name", Store()),
             value=set_value("~/tensorflow_datasets"),
             expr=None,
             expr_target=None,
             expr_annotation=None,
         ),
     )
예제 #10
0
def reindent_docstring(node, indent_level=1):
    """
    Reindent the docstring

    :param node: AST node
    :type node: ```ast.AST```

    :param indent_level: docstring indentation level whence: 0=no_tabs, 1=one tab; 2=two tabs
    :type indent_level: ```int```

    :returns: Node with reindent docstring
    :rtype: ```ast.AST```
    """
    doc_str = ast.get_docstring(node)
    if doc_str is not None:
        node.body[0] = ast.Expr(
            set_value("\n{tab}{s}\n{tab}".format(
                tab=tab * abs(indent_level),
                s="\n".join(
                    map(
                        lambda line: "{sep}{line}".format(sep=tab * 2,
                                                          line=line.lstrip())
                        if line.startswith(tab)
                        and len(line) > len(tab) and line[len(tab):line.lstrip(
                        ).find(" ") + len(tab)].rstrip(":s") not in frozenset(
                            (False, ) + TOKENS.rest) else line,
                        reindent(doc_str).splitlines(),
                    )),
            )))
    return node
예제 #11
0
    def test__set_name_and_type(self) -> None:
        """
        Tests that `_set_name_and_type` parsed AST code into a code str.
        Not working since I explicitly deleted the typ from ``` quoted defaults. Changed mock to match.
        """
        self.assertTupleEqual(
            _set_name_and_type(
                (
                    "adder",
                    {
                        "default": BinOp(
                            set_value(5),
                            Mult(),
                            set_value(5),
                        ),
                    },
                ),
                infer_type=True,
                word_wrap=True,
            ),
            ("adder", {
                "default": "```(5 * 5)```"
            }),
        )

        self.assertTupleEqual(
            _set_name_and_type(
                (
                    "adder",
                    {
                        "default": BinOp(
                            set_value(5),
                            Mult(),
                            set_value(5),
                        ),
                        "doc": ["5", "b"],
                    },
                ),
                infer_type=True,
                word_wrap=True,
            ),
            ("adder", {
                "default": "```(5 * 5)```",
                "doc": "5b"
            }),
        )
예제 #12
0
    def test_param2ast_with_wrapped_default(self) -> None:
        """ Check that `param2ast` behaves correctly with a wrapped default """

        run_ast_test(
            self,
            param2ast(
                ("zion", {"typ": None, "default": set_value(NoneStr)}),
            ),
            gold=AnnAssign(
                annotation=Name("object", Load()),
                simple=1,
                target=Name("zion", Store()),
                value=set_value(None),
                expr=None,
                expr_target=None,
                expr_annotation=None,
            ),
        )
예제 #13
0
def _make_call_meth(body, return_type, param_names):
    """
    Construct a `__call__` method from the provided `body`

    :param body: The body, probably from a FunctionDef.body
    :type body: ```List[AST]```

    :param return_type: The return type of the parent symbol (probably class). Used to fill in `__call__` return.
    :type return_type: ```Optional[str]```

    :param param_names: Container of AST `id`s to match for rename
    :type param_names: ```Optional[Iterator[str]]```

    :return: Internal function for `__call__`
    :rtype: ```FunctionDef```
    """
    body_len = len(body)
    return_ = (ast.fix_missing_locations(
        RewriteName(param_names).visit(
            Return(get_value(ast.parse(return_type[3:-3]).body[0]),
                   expr=None))) if return_type is not None
               and len(return_type) > 6 and return_type.startswith("```")
               and return_type.endswith("```") else None)
    if body_len:
        if isinstance(body[0], Expr):
            doc_str = get_value(body[0].value)
            if isinstance(doc_str, str) and body_len > 0:
                body = (body[1:] if body_len > 1 else ([
                    set_value(doc_str.replace(":cvar", ":param"))
                    if return_ is None else return_
                ] if body_len == 1 else body))
    #         elif not isinstance(body[0], Return) and return_ is not None:
    #             body.append(return_)
    # elif return_ is not None:
    #     body = [return_]

    return FunctionDef(args=arguments(
        args=[set_arg("self")],
        defaults=[],
        kw_defaults=[],
        kwarg=None,
        kwonlyargs=[],
        posonlyargs=[],
        vararg=None,
        arg=None,
    ),
                       body=body,
                       decorator_list=[],
                       name="__call__",
                       returns=None,
                       arguments_args=None,
                       identifier_name=None,
                       stmt=None,
                       lineno=None,
                       **maybe_type_comment)
예제 #14
0
 def test_param2argparse_param_default_ast_binop(self) -> None:
     """
     Tests that param2argparse_param works to change the type based on the default
       whence said default is a non specially handled ast.AST
     """
     run_ast_test(
         gen_ast=param2argparse_param(
             (
                 "byo",
                 {
                     "default": BinOp(
                         set_value(5),
                         Mult(),
                         set_value(5),
                     ),
                     "typ": "str",
                 },
             ),
         ),
         gold=Expr(
             Call(
                 args=[set_value("--byo")],
                 func=Attribute(
                     Name("argument_parser", Load()),
                     "add_argument",
                     Load(),
                 ),
                 keywords=[
                     keyword(arg="required", value=set_value(True), identifier=None),
                     keyword(
                         arg="default",
                         value=set_value("```(5 * 5)```"),
                         identifier=None,
                     ),
                 ],
                 expr=None,
                 expr_func=None,
             )
         ),
         test_case_instance=self,
     )
예제 #15
0
 def test_param2argparse_param_default_ast_tuple(self) -> None:
     """
     Tests that param2argparse_param works to change the type based on the default
       whence said default is an ast.Tuple
     """
     run_ast_test(
         gen_ast=param2argparse_param((
             "byo",
             {
                 "default": Tuple(
                     elts=[],
                     ctx=Load(),
                     expr=None,
                 ),
                 "typ": "str",
             },
         ), ),
         gold=Expr(
             Call(
                 args=[set_value("--byo")],
                 func=Attribute(
                     Name("argument_parser", Load()),
                     "add_argument",
                     Load(),
                 ),
                 keywords=[
                     keyword(arg="type",
                             value=Name("loads", Load()),
                             identifier=None),
                     keyword(arg="required",
                             value=set_value(True),
                             identifier=None),
                     keyword(arg="default",
                             value=set_value("()"),
                             identifier=None),
                 ],
                 expr=None,
                 expr_func=None,
             )),
         test_case_instance=self,
     )
예제 #16
0
    def _merge_name_to_column(assign):
        """
        Merge `a = Column()` into `Column("a")`

        :param assign: Of form `a = Column()`
        :type assign: ```Assign```

        :returns: Unwrapped Call with name prepended
        :rtype: ```Call```
        """
        assign.value.args.insert(0, set_value(assign.targets[0].id))
        return assign.value
예제 #17
0
    def test_gen_with_imports_from_file_and_prepended_import(self) -> None:
        """ Tests `gen` with `imports_from_file` and `prepend` """

        output_filename = os.path.join(
            self.tempdir,
            "test_gen_with_imports_from_file_and_prepended_import_output.py",
        )
        with patch("sys.stdout", new_callable=StringIO), patch(
            "sys.stderr", new_callable=StringIO
        ):
            self.assertIsNone(
                gen(
                    name_tpl="{name}Config",
                    input_mapping="gen_test_module.input_map",
                    imports_from_file="gen_test_module",
                    type_="class",
                    prepend=_import_gen_test_module_str,
                    output_filename=output_filename,
                    emit_call=True,
                    emit_default_doc=False,
                )
            )

        with open(output_filename, "rt") as f:
            gen_ast = ast.parse(f.read())
        gold = Module(
            body=[
                _import_gen_test_module_ast,
                _import_star_from_input_ast,
                self.expected_class_ast,
                # self.input_module_ast.body[1],
                Assign(
                    targets=[Name("__all__", Store())],
                    value=List(
                        ctx=Load(),
                        elts=[set_value("FooConfig")],
                        expr=None,
                    ),
                    expr=None,
                    lineno=None,
                    **maybe_type_comment
                ),
            ],
            type_ignores=[],
            stmt=None,
        )
        run_ast_test(
            self,
            gen_ast=gen_ast,
            gold=gold,
        )
예제 #18
0
 def test_param2argparse_param_default_code_quoted(self) -> None:
     """
     Tests that param2argparse_param works to change the type based on the default
       whence said default is a code quoted str
     """
     run_ast_test(
         gen_ast=(param2argparse_param((
             "byo",
             {
                 "default": "```(4)```",
                 "typ": "str",
             },
         ), )),
         gold=Expr(
             Call(
                 args=[set_value("--byo")],
                 func=Attribute(
                     Name("argument_parser", Load()),
                     "add_argument",
                     Load(),
                 ),
                 keywords=[
                     keyword(arg="type",
                             value=Name("int", Load()),
                             identifier=None),
                     keyword(arg="required",
                             value=set_value(True),
                             identifier=None),
                     keyword(arg="default",
                             value=set_value(4),
                             identifier=None),
                 ],
                 expr=None,
                 expr_func=None,
             )),
         test_case_instance=self,
     )
예제 #19
0
    def test_param2ast_with_assign(self) -> None:
        """ Check that `param2ast` behaves correctly with a non annotated (typeless) input """

        run_ast_test(
            self,
            param2ast(("zion", {
                "typ": None
            }), ),
            gold=AnnAssign(
                annotation=Name("object", Load()),
                simple=1,
                target=Name("zion", Store()),
                value=set_value(None),
                expr=None,
                expr_target=None,
                expr_annotation=None,
            ),
        )
예제 #20
0
    def test_find_in_ast(self) -> None:
        """ Tests that `find_in_ast` successfully finds nodes in AST """

        run_ast_test(
            self,
            find_in_ast("ConfigClass.dataset_name".split("."), class_ast),
            AnnAssign(
                annotation=Name(
                    "str",
                    Load(),
                ),
                simple=1,
                target=Name("dataset_name", Store()),
                value=set_value("mnist"),
                expr=None,
                expr_target=None,
                expr_annotation=None,
            ),
        )
예제 #21
0
 def test_emit_arg(self) -> None:
     """ Tests that `arg` is emitted from `emit_arg` """
     self.assertIsInstance(
         class_with_method_and_body_types_ast.body[1].args.args[1], arg)
     self.assertIsInstance(
         emit_arg(
             class_with_method_and_body_types_ast.body[1].args.args[1]),
         arg)
     assign = Assign(targets=[Name("yup", Store())],
                     value=set_value("nup"),
                     expr=None,
                     **maybe_type_comment)
     gen_ast = emit_arg(assign)
     self.assertIsInstance(gen_ast, arg)
     run_ast_test(
         self,
         gen_ast=gen_ast,
         gold=set_arg("yup"),
     )
예제 #22
0
    def test_param2ast_with_bad_default(self) -> None:
        """ Check that `param2ast` behaves correctly with a bad default """

        run_ast_test(
            self,
            param2ast(
                (
                    "stateful_metrics",
                    {"typ": "NoneType", "default": "the `Model`'s metrics"},
                ),
            ),
            gold=AnnAssign(
                annotation=Name("NoneType", Load()),
                simple=1,
                target=Name("stateful_metrics", Store()),
                value=set_value("```the `Model`'s metrics```"),
                expr=None,
                expr_annotation=None,
                expr_target=None,
            ),
        )
예제 #23
0
def sqlalchemy(class_def):
    """
    Parse out a `class C(Base): __tablename__=  'tbl'; dataset_name = Column(String, doc="p", primary_key=True)`,
        as constructed on an SQLalchemy declarative `Base`.

    :param class_def: A class inheriting from declarative `Base`, where `Base = sqlalchemy.orm.declarative_base()`
    :type class_def: ```Union[ClassDef]```

    :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(class_def, ClassDef)

    # Parse into the same format that `sqlalchemy_table` can read, then return with a call to it

    name = get_value(
        next(
            filter(
                lambda assign: any(
                    filter(
                        partial(eq, "__tablename__"),
                        map(attrgetter("id"), assign.targets),
                    )),
                filter(rpartial(isinstance, Assign), class_def.body),
            )).value)
    doc_string = get_docstring(class_def)

    def _merge_name_to_column(assign):
        """
        Merge `a = Column()` into `Column("a")`

        :param assign: Of form `a = Column()`
        :type assign: ```Assign```

        :returns: Unwrapped Call with name prepended
        :rtype: ```Call```
        """
        assign.value.args.insert(0, set_value(assign.targets[0].id))
        return assign.value

    return sqlalchemy_table(
        Call(
            func=Name("Table", Load()),
            args=list(
                chain.from_iterable((
                    iter((set_value(name), Name("metadata", Load()))),
                    map(
                        _merge_name_to_column,
                        filterfalse(
                            lambda assign: any(
                                map(
                                    lambda target: target.id == "__tablename__"
                                    or hasattr(target, "value") and isinstance(
                                        target.value, Call) and target.func.
                                    rpartition(".")[2] == "Column",
                                    assign.targets,
                                ), ),
                            filter(rpartial(isinstance, Assign),
                                   class_def.body),
                        ),
                    ),
                ))),
            keywords=[] if doc_string is None else [
                keyword(arg="comment",
                        value=set_value(doc_string),
                        identifier=None)
            ],
            expr=None,
            expr_func=None,
        ))
예제 #24
0
    Name,
    Return,
    Store,
    Tuple,
    arguments,
    fix_missing_locations,
    keyword,
)

from doctrans.ast_utils import FALLBACK_TYP, maybe_type_comment, set_arg, set_value
from doctrans.pure_utils import tab
from doctrans.tests.mocks.docstrings import docstring_header_str

argparse_add_argument_ast = Expr(
    Call(
        args=[set_value("--num")],
        func=Attribute(
            Name("argument_parser", Load()),
            "add_argument",
            Load(),
        ),
        keywords=[
            keyword(arg="type", value=Name("int", Load()), identifier=None),
            keyword(
                arg="required",
                value=set_value(True),
                identifier=None,
            ),
        ],
        expr=None,
        expr_func=None,
예제 #25
0
        "data_loader_kwargs",
        JSON,
        doc="pass this as arguments to data_loader function",
        default=None,
        nullable=True,
    ),
    comment={comment!r},
)
""".format(comment=docstring_header_and_return_str)

config_tbl_ast = Assign(
    targets=[Name("config_tbl", Store())],
    value=Call(
        func=Name("Table", Load()),
        args=[
            set_value("config_tbl"),
            Name("metadata", Load()),
            Call(
                func=Name("Column", Load()),
                args=[set_value("dataset_name"),
                      Name("String", Load())],
                keywords=[
                    keyword(arg="doc",
                            value=set_value("name of dataset"),
                            identifier=None),
                    keyword(arg="default",
                            value=set_value("mnist"),
                            identifier=None),
                    keyword(arg="primary_key",
                            value=set_value(True),
                            identifier=None),
예제 #26
0
def argparse_function(
    intermediate_repr,
    emit_default_doc=False,
    emit_default_doc_in_return=False,
    function_name="set_cli_args",
    function_type="static",
):
    """
    Convert to an argparse FunctionDef

    :param intermediate_repr: 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}),)]] }
    :type intermediate_repr: ```dict```

    :param emit_default_doc: Whether help/docstring should include 'With default' text
    :type emit_default_doc: ```bool```

    :param emit_default_doc_in_return: Whether help/docstring in return should include 'With default' text
    :type emit_default_doc_in_return: ```bool```

    :param function_name: name of function_def
    :type function_name: ```str```

    :param function_type: Type of function, static is static or global method, others just become first arg
    :type function_type: ```Literal['self', 'cls', 'static']```

    :return:  AST node for function definition which constructs argparse
    :rtype: ```FunctionDef```
    """
    function_name = function_name or intermediate_repr["name"]
    function_type = function_type or intermediate_repr["type"]
    internal_body = get_internal_body(
        target_name=function_name,
        target_type=function_type,
        intermediate_repr=intermediate_repr,
    )

    return FunctionDef(
        args=arguments(
            args=[set_arg("argument_parser")],
            # None if function_type in frozenset((None, "static"))
            # else set_arg(function_type),
            defaults=[],
            kw_defaults=[],
            kwarg=None,
            kwonlyargs=[],
            posonlyargs=[],
            vararg=None,
            arg=None,
        ),
        body=list(
            chain.from_iterable((
                iter((
                    Expr(
                        set_value(
                            "\n    Set CLI arguments\n\n    "
                            ":param argument_parser: argument parser\n    "
                            ":type argument_parser: ```ArgumentParser```\n\n    "
                            "{return_params}".format(return_params=(
                                lambda returns:
                                ":return: argument_parser{return_doc}\n    "
                                "{rtype}".format(
                                    return_doc=", {}".format(returns["doc"])
                                    if "doc" in returns else "",
                                    rtype=":rtype: ```ArgumentParser```\n    "
                                    if intermediate_repr["returns"][
                                        "return_type"].get("typ") in
                                    (None, "None", NoneStr) else
                                    ":rtype: ```Tuple[ArgumentParser, {returns[typ]}]```\n{tab}"
                                    "".format(returns=returns, tab=tab),
                                )
                            )(returns=set_default_doc(
                                next(iter(intermediate_repr["returns"].items())
                                     ),
                                emit_default_doc=emit_default_doc_in_return,
                            )[1]) if "return_type" in (
                                intermediate_repr.get("returns") or iter(())
                            ) else ":return: argument_parser\n    "
                                                     ":rtype: ```ArgumentParser```\n    "
                                                     ))),
                    Assign(targets=[
                        Attribute(
                            Name("argument_parser", Load()),
                            "description",
                            Store(),
                        )
                    ],
                           value=set_value(intermediate_repr["doc"]),
                           lineno=None,
                           expr=None,
                           **maybe_type_comment),
                )),
                filter(
                    None,
                    (
                        *((map(
                            partial(
                                param2argparse_param,
                                emit_default_doc=emit_default_doc,
                            ),
                            intermediate_repr["params"].items(),
                        )) if "params" in intermediate_repr else ()),
                        *(internal_body[
                            2 if len(internal_body) > 1 and isinstance(
                                internal_body[1], Assign) and internal_body[1].
                            targets[0].id == "argument_parser" else 1:] if
                          internal_body and isinstance(internal_body[0], Expr)
                          and isinstance(get_value(internal_body[0].value),
                                         str) else internal_body),
                        None if internal_body
                        and isinstance(internal_body[-1], Return) else (Return(
                            value=Tuple(
                                ctx=Load(),
                                elts=[
                                    Name("argument_parser", Load()),
                                    set_value(intermediate_repr["returns"]
                                              ["return_type"]["default"]) if
                                    intermediate_repr["returns"]["return_type"]
                                    ["default"].startswith("```")
                                    and intermediate_repr["returns"]
                                    ["return_type"]["default"].endswith("```")
                                    else ast.parse(intermediate_repr["returns"]
                                                   ["return_type"]
                                                   ["default"]).body[0].value,
                                ],
                                expr=None,
                            ),
                            expr=None,
                        ) if "default" in (
                            intermediate_repr.get("returns") or {
                                "return_type": iter(())
                            })["return_type"] else Return(value=Name(
                                "argument_parser", Load()),
                                                          expr=None)),
                    ),
                ),
            ))),
        decorator_list=[],
        name=function_name,
        returns=None,
        lineno=None,
        arguments_args=None,
        identifier_name=None,
        stmt=None,
        **maybe_type_comment)
예제 #27
0
def function(
    intermediate_repr,
    function_name,
    function_type,
    emit_default_doc=False,
    docstring_format="rest",
    indent_level=2,
    emit_separating_tab=PY3_8,
    inline_types=True,
    emit_as_kwonlyargs=True,
):
    """
    Construct a function from our IR

    :param intermediate_repr: 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}),)]] }
    :type intermediate_repr: ```dict```

    :param function_name: name of function_def
    :type function_name: ```Optional[str]```

    :param function_type: Type of function, static is static or global method, others just become first arg
    :type function_type: ```Optional[Literal['self', 'cls', 'static']]```

    :param docstring_format: Format of docstring
    :type docstring_format: ```Literal['rest', 'numpy', 'google']```

    :param emit_default_doc: Whether help/docstring should include 'With default' text
    :type emit_default_doc: ```bool```

    :param indent_level: docstring indentation level whence: 0=no_tabs, 1=one tab; 2=two tabs
    :type indent_level: ```int```

    :param emit_separating_tab: docstring decider for whether to put a tab between :param and return and desc
    :type emit_separating_tab: ```bool```

    :param inline_types: Whether the type should be inline or in docstring
    :type inline_types: ```bool```

    :param emit_as_kwonlyargs: Whether argument(s) emitted must be keyword only
    :type emit_as_kwonlyargs: ```bool```

    :return: AST node for function definition
    :rtype: ```FunctionDef```
    """
    params_no_kwargs = tuple(
        filter(
            lambda param: not param[0].endswith("kwargs"),
            intermediate_repr["params"].items(),
        ))

    function_name = function_name or intermediate_repr["name"]
    function_type = function_type or intermediate_repr["type"]

    args = ([] if function_type in frozenset(
        (None, "static")) else [set_arg(function_type)])
    from doctrans.emitter_utils import ast_parse_fix

    args_from_params = list(
        map(
            lambda param: set_arg(
                annotation=(Name(param[1]["typ"], Load()) if param[1]["typ"] in
                            simple_types else ast_parse_fix(param[1]["typ"]))
                if inline_types and "typ" in param[1] else None,
                arg=param[0],
            ),
            params_no_kwargs,
        ), )
    defaults_from_params = list(
        map(
            lambda param: set_value(None) if param[1].get("default") in
            (None, "None", NoneStr) else set_value(param[1].get("default")),
            params_no_kwargs,
        ))
    if emit_as_kwonlyargs:
        kwonlyargs, kw_defaults, defaults = args_from_params, defaults_from_params, []
    else:
        kwonlyargs, kw_defaults, defaults = [], [], defaults_from_params
        args += args_from_params

    internal_body = get_internal_body(
        target_name=function_name,
        target_type=function_type,
        intermediate_repr=intermediate_repr,
    )
    return_val = (Return(
        value=ast.parse(intermediate_repr["returns"]["return_type"]
                        ["default"].strip("`")).body[0].value,
        expr=None,
    ) if (intermediate_repr.get("returns") or {
        "return_type": {}
    })["return_type"].get("default") else None)

    return FunctionDef(
        args=arguments(
            args=args,
            defaults=defaults,
            kw_defaults=kw_defaults,
            kwarg=next(
                map(
                    lambda param: set_arg(param[0]),
                    filter(
                        lambda param: param[0].endswith("kwargs"),
                        intermediate_repr["params"].items(),
                    ),
                ),
                None,
            ),
            kwonlyargs=kwonlyargs,
            posonlyargs=[],
            vararg=None,
            arg=None,
        ),
        body=list(
            filter(
                None,
                (
                    Expr(
                        set_value(
                            to_docstring(
                                intermediate_repr,
                                emit_default_doc=emit_default_doc,
                                docstring_format=docstring_format,
                                emit_types=not inline_types,
                                indent_level=indent_level,
                                emit_separating_tab=emit_separating_tab,
                            ))),
                    *(internal_body[:-1] if internal_body
                      and isinstance(internal_body[-1], Return) and return_val
                      else internal_body),
                    return_val,
                ),
            )),
        decorator_list=[],
        name=function_name,
        returns=(ast.parse(
            intermediate_repr["returns"]["return_type"]["typ"]).body[0].value
                 if inline_types and (intermediate_repr.get("returns") or {
                     "return_type": {}
                 })["return_type"].get("typ") else None),
        lineno=None,
        arguments_args=None,
        identifier_name=None,
        stmt=None,
        **maybe_type_comment)
예제 #28
0
def class_(
        intermediate_repr,
        emit_call=False,
        class_name="ConfigClass",
        class_bases=("object", ),
        decorator_list=None,
        emit_default_doc=False,
):
    """
    Construct a class

    :param intermediate_repr: 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}),)]] }
    :type intermediate_repr: ```dict```

    :param emit_call: Whether to emit a `__call__` method from the `_internal` IR subdict
    :type emit_call: ```bool```

    :param class_name: name of class
    :type class_name: ```str```

    :param class_bases: bases of class (the generated class will inherit these)
    :type class_bases: ```Iterable[str]```

    :param decorator_list: List of decorators
    :type decorator_list: ```Optional[Union[List[Str], List[]]]```

    :param emit_default_doc: Whether help/docstring should include 'With default' text
    :type emit_default_doc: ```bool```

    :return: Class AST of the docstring
    :rtype: ```ClassDef```
    """
    returns = (intermediate_repr["returns"] if "return_type"
               in intermediate_repr.get("returns", {}) else OrderedDict())

    param_names = frozenset(intermediate_repr["params"].keys())
    if returns:
        intermediate_repr["params"].update(returns)
        del intermediate_repr["returns"]

    internal_body = intermediate_repr.get("_internal", {}).get("body", [])
    # TODO: Add correct classmethod/staticmethod to decorate function using `annotate_ancestry` and first-field checks
    # Such that the `self.` or `cls.` rewrite only applies to non-staticmethods
    # assert internal_body, "Expected `internal_body` to have contents"
    if internal_body and param_names:
        internal_body = list(
            map(
                ast.fix_missing_locations,
                map(RewriteName(param_names).visit, internal_body),
            ))

    return ClassDef(
        bases=list(map(rpartial(Name, Load()), class_bases)),
        body=list(
            chain.from_iterable((
                (Expr(
                    set_value(
                        to_docstring(
                            intermediate_repr,
                            indent_level=0,
                            emit_separating_tab=False,
                            emit_default_doc=emit_default_doc,
                            emit_types=False,
                        ).replace("\n:param ",
                                  "{tab}:cvar ".format(tab=tab)).replace(
                                      "{tab}:cvar ".format(tab=tab),
                                      "\n{tab}:cvar ".format(tab=tab),
                                      1,
                                  ).rstrip())), ),
                map(param2ast, intermediate_repr["params"].items()),
                iter((_make_call_meth(
                    internal_body,
                    returns["return_type"]["default"] if "default" in (
                        (returns or {
                            "return_type": iter(())
                        }).get("return_type") or iter(())) else None,
                    param_names,
                ), ) if emit_call and internal_body else tuple()),
            ))),
        decorator_list=list(map(rpartial(Name, Load()), decorator_list))
        if decorator_list else [],
        keywords=[],
        name=class_name,
        expr=None,
        identifier_name=None,
    )
예제 #29
0
def param_to_sqlalchemy_column_call(param, include_name):
    """
    Turn a param into a `Column(…)`

    :param param: Name, dict with keys: 'typ', 'doc', 'default'
    :type param: ```Tuple[str, dict]```

    :param include_name: Whether to include the name (exclude in declarative base)
    :type include_name: ```bool```

    :returns: Form of: `Column(…)`
    :rtype: ```Call```
    """
    print("param_to_sqlalchemy_column_call::include_name:", include_name, ";")
    name, _param = param
    del param

    args, keywords, nullable = [], [], None

    if _param["typ"].startswith("Optional["):
        _param["typ"] = _param["typ"][len("Optional["):-1]
        nullable = True

    if include_name:
        args.append(set_value(name))

    if "Literal[" in _param["typ"]:
        parsed_typ = get_value(ast.parse(_param["typ"]).body[0])
        assert (parsed_typ.value.id == "Literal"
                ), "Only basic Literal support is implemented, not {}".format(
                    parsed_typ.value.id)
        args.append(
            Call(
                func=Name("Enum", Load()),
                args=get_value(parsed_typ.slice).elts,
                keywords=[
                    keyword(arg="name", value=set_value(name), identifier=None)
                ],
                expr=None,
                expr_func=None,
            ))

    else:
        args.append(Name(typ2column_type[_param["typ"]], Load()))

    has_default = _param.get("default", ast) is not ast
    pk = _param.get("doc", "").startswith("[PK]")
    if pk:
        _param["doc"] = _param["doc"][4:].lstrip()
    elif has_default and _param["default"] not in none_types:
        nullable = False

    keywords.append(
        keyword(arg="doc",
                value=set_value(_param["doc"].rstrip(".")),
                identifier=None))

    if has_default:
        if _param["default"] == NoneStr:
            _param["default"] = None
        keywords.append(
            keyword(
                arg="default",
                value=set_value(_param["default"]),
                identifier=None,
            ))

    # Sorting :\
    if pk:
        keywords.append(
            keyword(arg="primary_key", value=set_value(True),
                    identifier=None), )

    if isinstance(nullable, bool):
        keywords.append(
            keyword(arg="nullable", value=set_value(nullable),
                    identifier=None))

    return Call(
        func=Name("Column", Load()),
        args=args,
        keywords=keywords,
        expr=None,
        expr_func=None,
    )
예제 #30
0
def generate_repr_method(params, cls_name, docstring_format):
    """
    Generate a `__repr__` method with all params, using `str.format` syntax

    :param params: an `OrderedDict` of form
        OrderedDict[str, {'typ': str, 'doc': Optional[str], 'default': Any}]
    :type params: ```OrderedDict```

    :param cls_name: Name of class
    :type cls_name: ```str```

    :param docstring_format: Format of docstring
    :type docstring_format: ```Literal['rest', 'numpydoc', 'google']```

    :returns: `__repr__` method
    :rtype: ```FunctionDef```
    """
    keys = tuple(params.keys())
    return FunctionDef(
        name="__repr__",
        args=arguments(
            posonlyargs=[],
            arg=None,
            args=[
                arg(arg="self",
                    annotation=None,
                    expr=None,
                    identifier_arg=None,
                    **maybe_type_comment)
            ],
            kwonlyargs=[],
            kw_defaults=[],
            defaults=[],
            vararg=None,
            kwarg=None,
        ),
        body=[
            Expr(
                set_value(docstring_repr_str if docstring_format ==
                          "rest" else docstring_repr_google_str)),
            Return(
                value=Call(
                    func=Attribute(
                        set_value("{cls_name}({format_args})".format(
                            cls_name=cls_name,
                            format_args=", ".join(
                                map("{0}={{{0}!r}}".format, keys)),
                        )),
                        "format",
                        Load(),
                    ),
                    args=[],
                    keywords=list(
                        map(
                            lambda key: keyword(
                                arg=key,
                                value=Attribute(Name("self", Load()), key,
                                                Load()),
                                identifier=None,
                            ),
                            keys,
                        )),
                    expr=None,
                    expr_func=None,
                ),
                expr=None,
            ),
        ],
        decorator_list=[],
        arguments_args=None,
        identifier_name=None,
        stmt=None,
        lineno=None,
        returns=None,
        **maybe_type_comment)