Beispiel #1
0
def sqlalchemy_table(call_or_name):
    """
    Parse out a `sqlalchemy.Table`, or a `name = sqlalchemy.Table`, into the IR

    :param call_or_name: The call to `sqlalchemy.Table` or an assignment followed by the call
    :type call_or_name: ```Union[AnnAssign, Assign, Call]```

    :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(call_or_name, Assign):
        name, call_or_name = call_or_name.targets[0].id, call_or_name.value
    elif isinstance(call_or_name, AnnAssign):
        name, call_or_name = call_or_name.target.id, call_or_name.value
    else:
        if not isinstance(call_or_name, Call):
            call_or_name = get_value(call_or_name)
        name = get_value(call_or_name.args[0])

    # Binding should be same name as table… I guess?
    assert_equal(get_value(call_or_name.args[0]), name)

    comment = next(
        map(
            get_value,
            map(
                get_value, filter(lambda kw: kw.arg == "comment", call_or_name.keywords)
            ),
        ),
        None,
    )
    intermediate_repr = (
        {"type": None, "doc": "", "params": OrderedDict()}
        if comment is None
        else docstring(comment)
    )
    intermediate_repr["name"] = name
    assert isinstance(call_or_name, Call)
    assert_equal(call_or_name.func.id.rpartition(".")[2], "Table")
    assert len(call_or_name.args) > 2

    merge_ir = {
        "params": OrderedDict(map(column_call_to_param, call_or_name.args[2:])),
        "returns": None,
    }
    ir_merge(target=intermediate_repr, other=merge_ir)
    if intermediate_repr["returns"] and intermediate_repr["returns"].get(
        "return_type", {}
    ).get("doc"):
        intermediate_repr["returns"]["return_type"]["doc"] = extract_default(
            intermediate_repr["returns"]["return_type"]["doc"], emit_default_doc=False
        )[0]

    return intermediate_repr
Beispiel #2
0
    def _handle_function(self, node, doc_str):
        """
        Handle functions

        :param node: AsyncFunctionDef | FunctionDef
        :type node: ```Union[AsyncFunctionDef, FunctionDef]```

        :param doc_str: The docstring
        :type doc_str: ```Optional[str]```

        :returns: Same type as input with args, returns, and docstring potentially modified
        :rtype: ```Union[AsyncFunctionDef, FunctionDef]```
        """
        ir = parse_docstring(doc_str)
        ir_merge(ir, parse.function(node))
        ir["name"] = node.name
        indent_level = max(
            len(node._location) - 1,
            1)  # function docstrings always have at least 1 indent level
        _doc_str = emit.docstring(
            ir,
            emit_types=not self.type_annotations,
            emit_default_doc=False,
            docstring_format=self.docstring_format,
            indent_level=indent_level,
        )
        if _doc_str.isspace():
            if doc_str is not None:
                del node.body[0]
        else:
            set_docstring(_doc_str, False, node)
        if self.type_annotations:
            # Add annotations
            if ir["params"]:
                node.args.args = list(
                    map(
                        lambda _arg: set_arg(
                            _arg.arg,
                            annotation=to_annotation(
                                _arg.annotation
                                if _arg.annotation is not None else ir[
                                    "params"][_arg.arg].get("typ")),
                        ),
                        node.args.args,
                    ))
            if ("return_type" in (ir.get("returns") or iter(()))
                    and ir["returns"]["return_type"].get("typ") is not None):
                node.returns = to_annotation(
                    ir["returns"]["return_type"]["typ"])
        else:
            # Remove annotations
            node.args.args = list(
                map(set_arg, map(attrgetter("arg"), node.args.args)))
            node.returns = None

        node.body = list(map(self.visit, node.body))
        return node
Beispiel #3
0
def _merge_inner_function(
    class_def, infer_type, intermediate_repr, merge_inner_function
):
    """
    Merge the inner function if found within the class, with the class IR

    :param class_def: Class AST
    :type class_def: ```ClassDef```

    :param infer_type: Whether to try inferring the typ (from the default)
    :type infer_type: ```bool```

    :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 merge_inner_function: Name of inner function to merge. If None, merge nothing.
    :type merge_inner_function: ```Optional[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```
    """
    function_def = next(
        filter(
            lambda func: func.name == merge_inner_function,
            filter(rpartial(isinstance, FunctionDef), ast.walk(class_def)),
        ),
        None,
    )

    if function_def is not None:
        function_type = (
            "static" if not function_def.args.args else function_def.args.args[0].arg
        )
        inner_ir = function(
            function_def,
            function_name=merge_inner_function,
            function_type=function_type,
            infer_type=infer_type,
        )
        ir_merge(other=inner_ir, target=intermediate_repr)

    return intermediate_repr
Beispiel #4
0
 def test_ir_merge_same_len_returns(self) -> None:
     """Tests for `ir_merge` when target and non-target have same size and a return"""
     target = {
         "params": OrderedDict(),
         "returns": OrderedDict(((
             "return_type",
             {
                 "typ": "str"
             },
         ), )),
     }
     other = {
         "params": OrderedDict(),
         "returns": OrderedDict(((
             "return_type",
             {
                 "doc": "so stringy"
             },
         ), )),
     }
     self.assertDictEqual(
         ir_merge(deepcopy(target), other),
         {
             "params":
             OrderedDict(),
             "returns":
             OrderedDict((("return_type", {
                 "typ": "str",
                 "doc": "so stringy"
             }), )),
         },
     )
Beispiel #5
0
 def test_ir_merge_same_len(self) -> None:
     """Tests for `ir_merge` when target and non-target have same size"""
     target = {
         "params": OrderedDict((("something", {
             "typ": "str"
         }), ), ),
         "returns": None,
     }
     other = {
         "params": OrderedDict((("something", {
             "doc": "neat"
         }), ), ),
         "returns": None,
     }
     self.assertDictEqual(
         ir_merge(deepcopy(target), other),
         {
             "params":
             OrderedDict((("something", {
                 "doc": "neat",
                 "typ": "str"
             }), ), ),
             "returns":
             None,
         },
     )
Beispiel #6
0
 def test_ir_merge_empty(self) -> None:
     """Tests for `ir_merge` when both are empty"""
     target = {"params": OrderedDict(), "returns": None}
     other = {"params": OrderedDict(), "returns": None}
     self.assertDictEqual(
         ir_merge(target, other),
         target,
     )
Beispiel #7
0
 def test_ir_merge_other_empty(self) -> None:
     """Tests for `ir_merge` when only non-target is empty"""
     target = {
         "params": OrderedDict((("something", {}), ), ),
         "returns": None,
     }
     other = {"params": OrderedDict(), "returns": None}
     self.assertDictEqual(
         ir_merge(target, other),
         target,
     )
Beispiel #8
0
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
Beispiel #9
0
def _inspect(obj, name, word_wrap):
    """
    Uses the `inspect` module to figure out the IR from the input

    :param obj: Something in memory, like a class, function, variable
    :type obj: ```Any```

    :param name: Name of the object being inspected
    :type name: ```str```

    :param word_wrap: Whether to word-wrap. Set `DOCTRANS_LINE_LENGTH` to configure length.
    :type word_wrap: ```bool```

    :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```
    """

    doc = getdoc(obj) or ""
    sig = signature(obj)
    is_function = isfunction(obj)
    ir = docstring(doc, emit_default_doc=is_function) if doc else {}
    if not is_function and "type" in ir:
        del ir["type"]

    ir.update(
        {
            "name": name or obj.__qualname__
            if hasattr(obj, "__qualname__")
            else obj.__name__,
            "params": OrderedDict(
                filter(
                    None,
                    map(
                        partial(_inspect_process_ir_param, sig=sig),
                        ir.get("params", {}).items(),
                    )
                    # if ir.get("params")
                    # else map(_inspect_process_sig, sig.parameters.items()),
                )
            ),
        }
    )

    src = get_source(obj)
    if src is None:
        return ir
    parsed_body = ast.parse(src.lstrip()).body[0]

    if is_function:
        ir["type"] = {"self": "self", "cls": "cls"}.get(
            next(iter(sig.parameters.values())).name, "static"
        )
        parser = function
    else:
        parser = class_

    other = parser(parsed_body)
    ir_merge(ir, other)
    if "return_type" in (ir.get("returns") or iter(())):
        ir["returns"] = OrderedDict(
            map(
                partial(_set_name_and_type, infer_type=False, word_wrap=word_wrap),
                ir["returns"].items(),
            )
        )

    return ir
Beispiel #10
0
def _class_from_memory(
    class_def, class_name, infer_type, merge_inner_function, word_wrap
):
    """
    Merge the inner function if found within the class, with the class IR.
    Internal func just for internal memory. Uses `inspect`.

    :param class_def: Class AST
    :type class_def: ```ClassDef```

    :param class_name: Class name
    :type class_name: ```str```

    :param infer_type: Whether to try inferring the typ (from the default)
    :type infer_type: ```bool```

    :param merge_inner_function: Name of inner function to merge. If None, merge nothing.
    :type merge_inner_function: ```Optional[str]```

    :param word_wrap: Whether to word-wrap. Set `DOCTRANS_LINE_LENGTH` to configure length.
    :type word_wrap: ```bool```

    :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```
    """
    ir = _inspect(class_def, class_name, word_wrap)
    src = get_source(class_def)
    if src is None:
        return ir
    parsed_body = ast.parse(src.lstrip()).body[0]
    parsed_body.body = (
        parsed_body.body if get_docstring(parsed_body) is None else parsed_body.body[1:]
    )
    if merge_inner_function is not None:
        _merge_inner_function(
            parsed_body,
            infer_type=infer_type,
            intermediate_repr=ir,
            merge_inner_function=merge_inner_function,
        )
        return ir
    ir["_internal"] = {
        "body": list(
            filterfalse(
                rpartial(isinstance, AnnAssign),
                parsed_body.body,
            )
        ),
        "from_name": class_name,
        "from_type": "cls",
    }
    body_ir = class_(
        class_def=parsed_body,
        class_name=class_name,
        merge_inner_function=merge_inner_function,
    )

    ir_merge(ir, body_ir)
    return ir