示例#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
示例#2
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]```

    :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```
    """
    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
示例#3
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"
             }), )),
         },
     )
示例#4
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,
         },
     )
示例#5
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,
     )
示例#6
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,
     )
示例#7
0
def class_(
    class_def,
    class_name=None,
    merge_inner_function=None,
    infer_type=False,
    word_wrap=True,
):
    """
    Converts an AST to our IR

    :param class_def: Class AST or Module AST with a ClassDef inside
    :type class_def: ```Union[Module, ClassDef]```

    :param class_name: Name of `class`. If None, gives first found.
    :type class_name: ```Optional[str]```

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

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

    :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 not isinstance(class_def, FunctionDef)
    is_supported_ast_node = isinstance(class_def, (Module, ClassDef))
    if not is_supported_ast_node and isinstance(class_def, type):
        ir = _inspect(class_def, class_name, word_wrap)
        parsed_body = ast.parse(getsource(class_def).lstrip()).body[0]
        parsed_body.body = (parsed_body.body
                            if ast.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

    assert (is_supported_ast_node
            ), "Expected 'Union[Module, ClassDef]' got `{!r}`".format(
                type(class_def).__name__)
    class_def = find_ast_type(class_def, class_name)
    doc_str = get_docstring(class_def)
    intermediate_repr = ({
        "name": class_name,
        "type": "static",
        "doc": "",
        "params": OrderedDict(),
        "returns": None,
    } if doc_str is None else docstring(get_docstring(class_def).replace(
        ":cvar", ":param"),
                                        emit_default_doc=False))

    if "return_type" in intermediate_repr["params"]:
        intermediate_repr["returns"] = OrderedDict(
            (("return_type",
              intermediate_repr["params"].pop("return_type")), ))

    body = class_def.body if doc_str is None else class_def.body[1:]
    for e in body:
        if isinstance(e, AnnAssign):
            typ = to_code(e.annotation).rstrip("\n")
            val = (lambda v: {
                "default": NoneStr
            } if v is None else {
                "default":
                v if type(v).__name__ in simple_types else (lambda value: {
                    "{}": {} if isinstance(v, Dict) else set(),
                    "[]": [],
                    "()": (),
                }.get(value, parse_to_scalar(value)))(to_code(v).rstrip("\n"))
            })(get_value(get_value(e)))
            # if 'str' in typ and val: val["default"] = val["default"].strip("'")  # Unquote?
            typ_default = dict(typ=typ, **val)

            for key in "params", "returns":
                if e.target.id in (intermediate_repr[key] or iter(())):
                    intermediate_repr[key][e.target.id].update(typ_default)
                    typ_default = False
                    break

            if typ_default:
                k = "returns" if e.target.id == "return_type" else "params"
                if intermediate_repr.get(k) is None:
                    intermediate_repr[k] = OrderedDict()
                intermediate_repr[k][e.target.id] = typ_default
        elif isinstance(e, Assign):
            val = get_value(e)
            if val is not None:
                val = get_value(val)
                deque(
                    map(
                        lambda target: setitem(*(
                            (intermediate_repr["params"][target.id], "default",
                             val)
                            if target.id in intermediate_repr["params"] else (
                                intermediate_repr["params"],
                                target.id,
                                {
                                    "default": val
                                },
                            ))),
                        e.targets,
                    ),
                    maxlen=0,
                )

    intermediate_repr.update({
        "params":
        OrderedDict(
            map(
                partial(_set_name_and_type,
                        infer_type=infer_type,
                        word_wrap=word_wrap),
                intermediate_repr["params"].items(),
            )),
        "_internal": {
            "body":
            list(filterfalse(rpartial(isinstance, (AnnAssign, Assign)), body)),
            "from_name":
            class_def.name,
            "from_type":
            "cls",
        },
    })

    if merge_inner_function is not None:
        assert isinstance(class_def, ClassDef)

        _merge_inner_function(
            class_def,
            infer_type=infer_type,
            intermediate_repr=intermediate_repr,
            merge_inner_function=merge_inner_function,
        )

    # intermediate_repr['_internal']["body"]= list(filterfalse(rpartial(isinstance,(AnnAssign,Assign)),class_def.body))

    return intermediate_repr
示例#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 `{!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
示例#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["params"].items(),
                )
                # if ir.get("params")
                # else map(_inspect_process_sig, sig.parameters.items()),
            )),
    })

    parsed_body = ast.parse(getsource(obj).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(),
            ))

    # if ir.get("returns") and "returns" not in ir["returns"]:
    #     if sig.return_annotation is not _empty:
    #         ir["returns"]["return_type"]["typ"] = lstrip_typings("{!s}".format(sig.return_annotation))
    #
    #     return_q = deque(
    #         filter(
    #             rpartial(isinstance, ast.Return),
    #             ast.walk(parsed_body),
    #         ),
    #         maxlen=1,
    #     )
    #     if return_q:
    #         return_val = get_value(return_q.pop())
    #         ir["returns"]["return_type"]["default"] = get_value(return_val)
    #         if not isinstance(
    #             ir["returns"]["return_type"]["default"],
    #             (str, int, float, complex, ast.Num, ast.Str, ast.Constant),
    #         ):
    #             ir["returns"]["return_type"]["default"] = "```{}```".format(
    #                 to_code(ir["returns"]["return_type"]["default"]).rstrip("\n")
    #             )
    return ir