Skip to content

API Reference

cdd

cdd

Root init

Parameters:

Name Type Description Default

cdd.argparse_function

cdd.argparse_function

Argparse function parser and emitter

Parameters:

Name Type Description Default

cdd.argparse_function.emit

cdd.argparse_function.emit

Argparse emitter

Parameters:

Name Type Description Default

argparse_function

argparse_function(intermediate_repr, emit_default_doc=False, function_name='set_cli_args', function_type='static', wrap_description=False, word_wrap=True, docstring_format='rest')

Convert to an argparse FunctionDef

Parameters:

Name Type Description Default
intermediate_repr dict

a dictionary consistent with IntermediateRepr, defined as: ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any}) IntermediateRepr = TypedDict("IntermediateRepr", { "name": Optional[str], "type": Optional[str], "doc": Optional[str], "params": OrderedDict[str, ParamVal], "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]], })

required
emit_default_doc bool

Whether help/docstring should include 'With default' text

required
function_name name

name of function_def

required
function_type Literal['self', 'cls', 'static']

Type of function, static is static or global method, others just become first arg

required
wrap_description bool

Whether to word-wrap the description. Set DOCTRANS_LINE_LENGTH to configure length.

required
word_wrap bool

Whether to word-wrap. Set DOCTRANS_LINE_LENGTH to configure length.

required
docstring_format Literal['rest', 'numpydoc', 'google']

Format of docstring

required

Returns:

Name Type Description
return_type FunctionDef

AST node for function definition which constructs argparse

Source code in cdd/argparse_function/emit.py
def argparse_function(
    intermediate_repr,
    emit_default_doc=False,
    function_name="set_cli_args",
    function_type="static",
    wrap_description=False,
    word_wrap=True,
    docstring_format="rest",
):
    """
    Convert to an argparse FunctionDef

    :param intermediate_repr: a dictionary consistent with `IntermediateRepr`, defined as:
        ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any})
        IntermediateRepr = TypedDict("IntermediateRepr", {
            "name": Optional[str],
            "type": Optional[str],
            "doc": Optional[str],
            "params": OrderedDict[str, ParamVal],
            "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]],
        })
    :type intermediate_repr: ```dict```

    :param emit_default_doc: Whether help/docstring should include 'With default' text
    :type emit_default_doc: ```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']```

    :param wrap_description: Whether to word-wrap the description. Set `DOCTRANS_LINE_LENGTH` to configure length.
    :type wrap_description: ```bool```

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

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

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

    return FunctionDef(
        args=arguments(
            args=[cdd.shared.ast_utils.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(
                                cdd.shared.ast_utils.set_value(
                                    docstring(
                                        {
                                            "doc": "Set CLI arguments",
                                            "params": OrderedDict(
                                                (
                                                    (
                                                        "argument_parser",
                                                        {
                                                            "doc": "argument parser",
                                                            "typ": "ArgumentParser",
                                                        },
                                                    ),
                                                )
                                            ),
                                            "returns": OrderedDict(
                                                (
                                                    (
                                                        "return_type",
                                                        (
                                                            {
                                                                "doc": (
                                                                    "argument_parser, {returns_doc}".format(
                                                                        returns_doc=intermediate_repr[
                                                                            "returns"
                                                                        ][
                                                                            "return_type"
                                                                        ][
                                                                            "doc"
                                                                        ]
                                                                    )
                                                                    if intermediate_repr[
                                                                        "returns"
                                                                    ][
                                                                        "return_type"
                                                                    ].get(
                                                                        "doc"
                                                                    )
                                                                    else "argument_parser"
                                                                ),
                                                                "typ": "Tuple[ArgumentParser, {typ}]".format(
                                                                    typ=intermediate_repr[
                                                                        "returns"
                                                                    ][
                                                                        "return_type"
                                                                    ][
                                                                        "typ"
                                                                    ]
                                                                ),
                                                            }
                                                            if "return_type"
                                                            in (
                                                                (
                                                                    intermediate_repr
                                                                    or {}
                                                                ).get("returns")
                                                                or iter(())
                                                            )
                                                            and intermediate_repr[
                                                                "returns"
                                                            ]["return_type"].get("typ")
                                                            not in none_types
                                                            else {
                                                                "doc": (
                                                                    "argument_parser"
                                                                ),
                                                                "typ": "ArgumentParser",
                                                            }
                                                        ),
                                                    ),
                                                ),
                                            ),
                                        },
                                        docstring_format=docstring_format,
                                        word_wrap=word_wrap,
                                        indent_level=1,
                                    )
                                ),
                                lineno=None,
                                col_offset=None,
                            ),
                            Assign(
                                targets=[
                                    Attribute(
                                        Name(
                                            "argument_parser",
                                            Load(),
                                            lineno=None,
                                            col_offset=None,
                                        ),
                                        "description",
                                        Store(),
                                        lineno=None,
                                        col_offset=None,
                                    )
                                ],
                                value=cdd.shared.ast_utils.set_value(
                                    (fill if wrap_description else identity)(
                                        intermediate_repr["doc"]
                                    )
                                ),
                                lineno=None,
                                expr=None,
                                **cdd.shared.ast_utils.maybe_type_comment
                            ),
                        )
                    ),
                    filter(
                        None,
                        (
                            *(
                                (
                                    map(
                                        partial(
                                            cdd.shared.ast_utils.param2argparse_param,
                                            word_wrap=word_wrap,
                                            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(
                                    cdd.shared.ast_utils.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(),
                                                    lineno=None,
                                                    col_offset=None,
                                                ),
                                                (
                                                    cdd.shared.ast_utils.set_value(
                                                        intermediate_repr["returns"][
                                                            "return_type"
                                                        ]["default"]
                                                    )
                                                    if code_quoted(
                                                        intermediate_repr["returns"][
                                                            "return_type"
                                                        ]["default"]
                                                    )
                                                    else ast.parse(
                                                        intermediate_repr["returns"][
                                                            "return_type"
                                                        ]["default"]
                                                    )
                                                    .body[0]
                                                    .value
                                                ),
                                            ],
                                            expr=None,
                                            lineno=None,
                                            col_offset=None,
                                        ),
                                        expr=None,
                                    )
                                    if "default"
                                    in (
                                        intermediate_repr.get("returns")
                                        or {"return_type": iter(())}
                                    )["return_type"]
                                    else Return(
                                        value=Name(
                                            "argument_parser",
                                            Load(),
                                            lineno=None,
                                            col_offset=None,
                                        ),
                                        expr=None,
                                    )
                                )
                            ),
                        ),
                    ),
                )
            )
        ),
        decorator_list=[],
        type_params=[],
        name=function_name,
        returns=None,
        lineno=None,
        arguments_args=None,
        identifier_name=None,
        stmt=None,
        **cdd.shared.ast_utils.maybe_type_comment
    )

cdd.argparse_function.parse

cdd.argparse_function.parse

Argparse function parser

Parameters:

Name Type Description Default

argparse_ast

argparse_ast(function_def, function_type=None, function_name=None, parse_original_whitespace=False, word_wrap=False)

Converts an argparse AST to our IR

Parameters:

Name Type Description Default
function_def FunctionDef

AST of argparse function_def

required
function_type Literal['self', 'cls', 'static']

Type of function, static is static or global method, others just become first arg

required
function_name name

name of function_def

required
parse_original_whitespace bool

Whether to parse original whitespace or strip it out

required
word_wrap bool

Whether to word-wrap. Set DOCTRANS_LINE_LENGTH to configure length.

required

Returns:

Name Type Description
return_type dict

a dictionary consistent with IntermediateRepr, defined as: ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any}) IntermediateRepr = TypedDict("IntermediateRepr", { "name": Optional[str], "type": Optional[str], "doc": Optional[str], "params": OrderedDict[str, ParamVal], "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]], })

Source code in cdd/argparse_function/parse.py
def argparse_ast(
    function_def,
    function_type=None,
    function_name=None,
    parse_original_whitespace=False,
    word_wrap=False,
):
    """
    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```

    :param parse_original_whitespace: Whether to parse original whitespace or strip it out
    :type parse_original_whitespace: ```bool```

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

    :return: a dictionary consistent with `IntermediateRepr`, defined as:
        ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any})
        IntermediateRepr = TypedDict("IntermediateRepr", {
            "name": Optional[str],
            "type": Optional[str],
            "doc": Optional[str],
            "params": OrderedDict[str, ParamVal],
            "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]],
        })
    :rtype: ```dict```
    """
    assert isinstance(
        function_def, FunctionDef
    ), "Expected `FunctionDef` got `{node_name!r}`".format(
        node_name=type(function_def).__name__
    )

    doc_string: Optional[str] = get_docstring(
        function_def, clean=parse_original_whitespace
    )
    intermediate_repr: IntermediateRepr = {
        "name": function_name or function_def.name,
        "type": function_type or get_function_type(function_def),
        "doc": "",
        "params": OrderedDict(),
    }
    ir: IntermediateRepr = parse_docstring(
        doc_string,
        word_wrap=word_wrap,
        emit_default_doc=True,
        parse_original_whitespace=parse_original_whitespace,
    )

    # 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: FunctionDef.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: bool = 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[Call] = cast(
        List[Call],
        list(
            filterfalse(
                is_argparse_description,
                filterfalse(is_argparse_add_argument, body),
            )
        ),
    )
    if inner_body:
        intermediate_repr["_internal"] = {
            "original_doc_str": (
                doc_string
                if parse_original_whitespace
                else get_docstring(function_def, clean=False)
            ),
            "body": inner_body,
            "from_name": function_def.name,
            "from_type": "static",
        }

    return intermediate_repr

cdd.argparse_function.utils

cdd.argparse_function.utils

Argparse function parser and emitter utility module

Parameters:

Name Type Description Default

cdd.argparse_function.utils.emit_utils

cdd.argparse_function.utils.emit_utils

Utility functions for cdd.emit.argparse_function

Parameters:

Name Type Description Default

parse_out_param

parse_out_param(expr, require_default=False, emit_default_doc=True)

Turns the class_def repr of '--dataset_name', type=str, help='name of dataset.', required=True, default='mnist' into Tuple[Literal['dataset_name'], {"typ": Literal["str"], "doc": Literal["name of dataset."], "default": Literal["mnist"]}]

Parameters:

Name Type Description Default
expr Expr

Expr

required
require_default bool

Whether a default is required, if not found in doc, infer the proper default from type

required
emit_default_doc bool

Whether help/docstring should include 'With default' text

required

Returns:

Name Type Description
return_type tuple[str, dict]

Name, dict with keys: 'typ', 'doc', 'default'

Source code in cdd/argparse_function/utils/emit_utils.py
def parse_out_param(expr, require_default=False, emit_default_doc=True):
    """
    Turns the class_def repr of '--dataset_name', type=str, help='name of dataset.', required=True, default='mnist'
      into
           Tuple[Literal['dataset_name'], {"typ": Literal["str"], "doc": Literal["name of dataset."],
                                           "default": Literal["mnist"]}]

    :param expr: Expr
    :type expr: ```Expr```

    :param require_default: Whether a default is required, if not found in doc, infer the proper default from type
    :type require_default: ```bool```

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

    :return: Name, dict with keys: 'typ', 'doc', 'default'
    :rtype: ```tuple[str, dict]```
    """
    required: bool = get_value(
        get_value(
            next(
                (
                    keyword
                    for keyword in expr.value.keywords
                    if keyword.arg == "required"
                ),
                set_value(False),
            )
        )
    )

    typ: str = next(
        (
            _handle_value(get_value(key_word))
            for key_word in expr.value.keywords
            if key_word.arg == "type"
        ),
        "str",
    )
    name: str = get_value(expr.value.args[0])[len("--") :]
    default: Optional[Any] = next(
        (
            get_value(key_word.value)
            for key_word in expr.value.keywords
            if key_word.arg == "default"
        ),
        None,
    )
    doc: Optional[str] = (
        lambda help_: (
            help_
            if help_ is None
            else (
                help_
                if default is None
                or emit_default_doc is False
                or (hasattr(default, "__len__") and len(default) == 0)
                or "defaults to" in help_
                or "Defaults to" in help_
                else "{help} Defaults to {default}".format(
                    help=help_ if help_.endswith(".") else "{}.".format(help_),
                    default=default,
                )
            )
        )
    )(
        next(
            (
                get_value(key_word.value)
                for key_word in expr.value.keywords
                if key_word.arg == "help" and key_word.value
            ),
            None,
        )
    )
    if default is None:
        doc, default = extract_default(doc, emit_default_doc=emit_default_doc)
    if default is None:
        if required:
            # if name.endswith("kwargs"):
            #    default = NoneStr
            # else:
            default: Optional[
                Dict[Optional[str], Union[int, float, complex, str, bool, None]]
            ] = (simple_types[typ] if typ in simple_types else NoneStr)

        elif require_default:  # or typ.startswith("Optional"):
            default: Optional[
                Dict[Optional[str], Union[int, float, complex, str, bool, None]]
            ] = NoneStr

    action: Optional[Any] = next(
        (
            get_value(key_word.value)
            for key_word in expr.value.keywords
            if key_word.arg == "action"
        ),
        None,
    )

    typ: Optional[Any] = next(
        (
            _handle_keyword(keyword, typ)
            for keyword in expr.value.keywords
            if keyword.arg == "choices"
        ),
        typ,
    )
    if action == "append":
        typ: str = "List[{typ}]".format(typ=typ)

    if not required and "Optional" not in typ:
        typ: str = "Optional[{typ}]".format(typ=typ)

    return name, dict(
        doc=doc, typ=typ, **({} if default is None else {"default": default})
    )

cdd.class_

cdd.class_

class parser and emitter

Parameters:

Name Type Description Default

cdd.class_.emit

cdd.class_.emit

class emitter

Parameters:

Name Type Description Default

class_

class_(intermediate_repr, emit_call=False, class_name=None, class_bases=('object',), decorator_list=None, word_wrap=True, docstring_format='rest', emit_original_whitespace=False, emit_default_doc=False)

Construct a class

Parameters:

Name Type Description Default
intermediate_repr dict

a dictionary consistent with IntermediateRepr, defined as: ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any}) IntermediateRepr = TypedDict("IntermediateRepr", { "name": Optional[str], "type": Optional[str], "doc": Optional[str], "params": OrderedDict[str, ParamVal], "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]], })

required
emit_call bool

Whether to emit a __call__ method from the _internal IR subdict

required
class_name name

name of class

required
class_bases Iterable[str]

bases of class (the generated class will inherit these)

required
decorator_list Optional[List[str]]

List of decorators

required
word_wrap bool

Whether to word-wrap. Set DOCTRANS_LINE_LENGTH to configure length.

required
docstring_format Literal['rest', 'numpydoc', 'google']

Format of docstring

required
emit_original_whitespace bool

Whether to emit original whitespace or strip it out (in docstring)

required
emit_default_doc bool

Whether help/docstring should include 'With default' text

required

Returns:

Name Type Description
return_type ClassDef

Class AST

Source code in cdd/class_/emit.py
def class_(
    intermediate_repr,
    emit_call=False,
    class_name=None,
    class_bases=("object",),
    decorator_list=None,
    word_wrap=True,
    docstring_format="rest",
    emit_original_whitespace=False,
    emit_default_doc=False,
):
    """
    Construct a class

    :param intermediate_repr: a dictionary consistent with `IntermediateRepr`, defined as:
        ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any})
        IntermediateRepr = TypedDict("IntermediateRepr", {
            "name": Optional[str],
            "type": Optional[str],
            "doc": Optional[str],
            "params": OrderedDict[str, ParamVal],
            "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]],
        })
    :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[List[str]]```

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

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

    :param emit_original_whitespace: Whether to emit original whitespace or strip it out (in docstring)
    :type emit_original_whitespace: ```bool```

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

    :return: Class AST
    :rtype: ```ClassDef```
    """
    assert isinstance(
        intermediate_repr, dict
    ), "Expected `dict` got `{type_name}`".format(
        type_name=type(intermediate_repr).__name__
    )
    assert class_name or intermediate_repr["name"], "Class has no name"

    returns: OrderedDict = (
        intermediate_repr["returns"]
        if "return_type" in ((intermediate_repr or {}).get("returns") or iter(()))
        else OrderedDict()
    )
    if returns:
        intermediate_repr["params"].update(returns)
        del intermediate_repr["returns"]

    internal_body: ClassDef.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"
    param_names: Optional[FrozenSet[str]] = (
        frozenset(intermediate_repr["params"].keys())
        if "params" in intermediate_repr
        else None
    )
    if param_names:
        if internal_body:
            internal_body: ClassDef.body = list(
                map(
                    ast.fix_missing_locations,
                    map(RewriteName(param_names).visit, internal_body),
                )
            )
        elif (returns or {"return_type": None}).get("return_type") is not None:
            internal_body = returns["return_type"]

    indent_level: int = 1

    _emit_docstring = partial(
        docstring,
        docstring_format=docstring_format,
        indent_level=indent_level,
        emit_default_doc=emit_default_doc,
        emit_separating_tab=True,
        emit_types=False,
        word_wrap=word_wrap,
    )
    return ClassDef(
        bases=list(
            map(
                rpartial(partial(Name, lineno=None, col_offset=None), Load()),
                class_bases,
            )
        ),
        body=list(
            filter(
                None,
                chain.from_iterable(
                    (
                        (
                            (
                                lambda ds: (
                                    None
                                    if ds is None
                                    else Expr(
                                        cdd.shared.ast_utils.set_value(ds),
                                        lineno=None,
                                        col_offset=None,
                                    )
                                )
                            )(
                                _emit_docstring(
                                    {
                                        k: intermediate_repr[k]
                                        for k in intermediate_repr
                                        if k != "_internal"
                                    },
                                    emit_original_whitespace=emit_original_whitespace,
                                    purpose="class",
                                ).rstrip()
                                or None
                            ),
                        ),
                        map(
                            cdd.shared.ast_utils.param2ast,
                            (intermediate_repr.get("params") or OrderedDict()).items(),
                        ),
                        iter(
                            (
                                (
                                    (
                                        internal_body[0]
                                        if len(internal_body) == 1
                                        and isinstance(internal_body[0], FunctionDef)
                                        and internal_body[0].name == "__call__"
                                        else 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,
                                            docstring_format=docstring_format,
                                            word_wrap=word_wrap,
                                        )
                                    ),
                                )
                                or iter(())
                            )
                            if emit_call and internal_body
                            else iter(())
                        ),
                    ),
                ),
            )
        )
        or [
            Expr(
                Constant(Ellipsis) if PY_GTE_3_8 else Ellipsis,
                lineno=None,
                col_offset=None,
            )
        ],  # empty body will cause syntax error
        decorator_list=(
            list(
                map(
                    rpartial(partial(Name, lineno=None, col_offset=None), Load()),
                    decorator_list,
                )
            )
            if decorator_list
            else []
        ),
        type_params=[],
        keywords=[],
        name=class_name or intermediate_repr["name"],
        expr=None,
        identifier_name=None,
        lineno=None,
        col_offset=None,
    )

cdd.class_.parse

cdd.class_.parse

class parser

Parameters:

Name Type Description Default

class_

class_(class_def, class_name=None, merge_inner_function=None, infer_type=False, parse_original_whitespace=False, word_wrap=True)

Converts an AST to our IR

Parameters:

Name Type Description Default
class_def Union[Module, ClassDef]

Class AST or Module AST with a ClassDef inside

required
class_name Optional[str]

Name of class. If None, gives first found.

required
merge_inner_function Optional[str]

Name of inner function to merge. If None, merge nothing.

required
infer_type bool

Whether to try inferring the typ (from the default)

required
parse_original_whitespace bool

Whether to parse original whitespace or strip it out

required
word_wrap bool

Whether to word-wrap. Set DOCTRANS_LINE_LENGTH to configure length.

required

Returns:

Name Type Description
return_type dict

a dictionary consistent with IntermediateRepr, defined as: ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any}) IntermediateRepr = TypedDict("IntermediateRepr", { "name": Optional[str], "type": Optional[str], "doc": Optional[str], "params": OrderedDict[str, ParamVal], "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]], })

Source code in cdd/class_/parse.py
def class_(
    class_def,
    class_name=None,
    merge_inner_function=None,
    infer_type=False,
    parse_original_whitespace=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 parse_original_whitespace: Whether to parse original whitespace or strip it out
    :type parse_original_whitespace: ```bool```

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

    :return: a dictionary consistent with `IntermediateRepr`, defined as:
        ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any})
        IntermediateRepr = TypedDict("IntermediateRepr", {
            "name": Optional[str],
            "type": Optional[str],
            "doc": Optional[str],
            "params": OrderedDict[str, ParamVal],
            "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]],
        })
    :rtype: ```dict```
    """

    assert not isinstance(class_def, FunctionDef), "Expected not `FunctionDef`"
    is_supported_ast_node: bool = isinstance(class_def, (Module, ClassDef))
    if not is_supported_ast_node and isinstance(class_def, type):
        return _class_from_memory(
            class_def=class_def,
            class_name=class_name,
            infer_type=infer_type,
            merge_inner_function=merge_inner_function,
            parse_original_whitespace=parse_original_whitespace,
            word_wrap=word_wrap,
        )

    assert (
        is_supported_ast_node
    ), "Expected 'Union[Module, ClassDef]' got `{node_name!r}`".format(
        node_name=type(class_def).__name__
    )
    class_def: ClassDef = cast(
        ClassDef, cdd.shared.ast_utils.find_ast_type(class_def, class_name)
    )
    doc_str: Optional[str] = get_docstring(class_def, clean=parse_original_whitespace)
    intermediate_repr: IntermediateRepr = (
        {
            "name": class_name,
            "type": "static",
            "doc": "",
            "params": OrderedDict(),
            "returns": None,
        }
        if doc_str is None
        else cdd.docstring.parse.docstring(
            doc_str,
            emit_default_doc=False,
            parse_original_whitespace=parse_original_whitespace,
        )
    )

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

    body: ClassDef.body = class_def.body if doc_str is None else class_def.body[1:]
    for e in body:
        if isinstance(e, AnnAssign):
            typ: str = cdd.shared.source_transformer.to_code(e.annotation).rstrip("\n")
            val = (
                (
                    lambda v: (
                        {"default": cdd.shared.ast_utils.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,
                                        cdd.shared.ast_utils.parse_to_scalar(value),
                                    )
                                )(cdd.shared.source_transformer.to_code(v).rstrip("\n"))
                            )
                        }
                    )
                )(cdd.shared.ast_utils.get_value(cdd.shared.ast_utils.get_value(e)))
                if hasattr(e, "value") and e.value is not None
                else {}
            )

            # if 'str' in typ and val: val["default"] = val["default"].strip("'")  # Unquote?
            typ_default = (
                {"typ": typ} if val is None else dict(typ=typ, **val)
            )  # type: Union[bool, dict[str, Any]]

            target_id: str = e.target.id.lstrip("*")

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

            if typ_default:
                k: str = "returns" if target_id == "return_type" else "params"
                if intermediate_repr.get(k) is None:
                    intermediate_repr[k] = OrderedDict()
                intermediate_repr[k][target_id] = typ_default
        elif isinstance(e, Assign):
            val = cdd.shared.ast_utils.get_value(e)

            if val is not None:
                val = cdd.shared.ast_utils.get_value(val)
                deque(
                    map(
                        lambda target: setitem(
                            *(
                                (
                                    lambda _target_id: (
                                        (
                                            intermediate_repr["params"][_target_id],
                                            "default",
                                            val,
                                        )
                                        if isinstance(target, Name)
                                        and _target_id in intermediate_repr["params"]
                                        else (
                                            intermediate_repr["params"],
                                            (
                                                _target_id
                                                if isinstance(target, Name)
                                                else cdd.shared.ast_utils.get_value(
                                                    cdd.shared.ast_utils.get_value(
                                                        target
                                                    )
                                                )
                                            ),
                                            {"default": val},
                                        )
                                    )
                                )(
                                    target.id.lstrip("*")
                                    if hasattr(target, "id")
                                    else target.value.id
                                )
                            )
                        ),
                        e.targets,
                    ),
                    maxlen=0,
                )

    intermediate_repr.update(
        {
            "name": class_name or class_def.name,
            "params": OrderedDict(
                map(
                    partial(
                        cdd.shared.docstring_parsers._set_name_and_type,
                        infer_type=infer_type,
                        word_wrap=word_wrap,
                    ),
                    intermediate_repr["params"].items(),
                )
            ),
            "_internal": {
                "original_doc_str": (
                    doc_str
                    if parse_original_whitespace
                    else get_docstring(class_def, clean=False)
                ),
                "body": cast(
                    List[AST],
                    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
        ), "Expected `ClassDef` got `{node_name!r}`".format(
            node_name=type(class_def).__name__
        )

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

cdd.class_.utils

cdd.class_.utils

class parser and emitter utility module

Parameters:

Name Type Description Default

cdd.class_.utils.emit_utils

cdd.class_.utils.emit_utils

Utility functions for cdd.emit.class_

Parameters:

Name Type Description Default

RewriteName

RewriteName(node_ids)

Bases: NodeTransformer

A :class:NodeTransformer subclass that walks the abstract syntax tree and allows modification of nodes. Here it modifies parameter names to be self.param_name

Parameters:

Name Type Description Default

Set parameter

Parameters:

Name Type Description Default
node_ids id

Container of AST ids to match for rename

required
Source code in cdd/class_/utils/emit_utils.py
def __init__(self, node_ids):
    """
    Set parameter

    :param node_ids: Container of AST `id`s to match for rename
    :type node_ids: ```Optional[Iterator[str]]```
    """
    self.node_ids = node_ids

visit_Name

visit_Name(node)

Rename parameter name with a self. attribute prefix

Parameters:

Name Type Description Default
node Name

The AST node

required

Returns:

Name Type Description
return_type Union[Name, Attribute]

Name iff Name is not a parameter else Attribute

Source code in cdd/class_/utils/emit_utils.py
def visit_Name(self, node):
    """
    Rename parameter name with a `self.` attribute prefix

    :param node: The AST node
    :type node: ```Name```

    :return: `Name` iff `Name` is not a parameter else `Attribute`
    :rtype: ```Union[Name, Attribute]```
    """
    return (
        Attribute(
            Name("self", Load(), lineno=None, col_offset=None),
            node.id,
            Load(),
            lineno=None,
            col_offset=None,
        )
        if not self.node_ids or node.id in self.node_ids
        else ast.NodeTransformer.generic_visit(self, node)
    )

cdd.class_.utils.parse_utils

cdd.class_.utils.parse_utils

Utility functions for cdd.parse.class

Parameters:

Name Type Description Default

get_source

get_source(obj)

Call inspect.getsource and raise an error unless class definition could not be found

Parameters:

Name Type Description Default
obj Any

object to inspect

required

Returns:

Name Type Description
return_type Optional[str]

The source

Source code in cdd/class_/utils/parse_utils.py
def get_source(obj):
    """
    Call inspect.getsource and raise an error unless class definition could not be found

    :param obj: object to inspect
    :type obj: ```Any```

    :return: The source
    :rtype: ```Optional[str]```
    """
    try:
        return getsource(obj)
    except OSError as e:
        if e.args and e.args[0] in frozenset(
            (
                "could not find class definition",
                "source code not available",
                "could not get source code",
            )
        ):
            return None
        raise

cdd.class_.utils.shared_utils

cdd.class_.utils.shared_utils

Shared utility functions for cdd.class_

Parameters:

Name Type Description Default

ClassEmitProtocol

Bases: Protocol

Protocol for class emitter

Parameters:

Name Type Description Default

__call__

__call__(intermediate_repr: IntermediateRepr, emit_call: bool = False, class_name: Optional[str] = None, class_bases: Tuple[str] = ('object',), decorator_list: Optional[List[str]] = None, word_wrap: bool = True, docstring_format: Literal['rest', 'numpydoc', 'google'] = 'rest', emit_original_whitespace: bool = False, emit_default_doc: bool = False) -> ClassDef

Construct a class

Parameters:

Name Type Description Default
intermediate_repr dict

a dictionary consistent with IntermediateRepr, defined as: ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any}) IntermediateRepr = TypedDict("IntermediateRepr", { "name": Optional[str], "type": Optional[str], "doc": Optional[str], "params": OrderedDict[str, ParamVal], "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]], })

required
emit_call

Whether to emit a __call__ method from the _internal IR subdict

required
class_name name

name of class

required
class_bases

bases of class (the generated class will inherit these)

required
decorator_list

List of decorators

required
word_wrap

Whether to word-wrap. Set DOCTRANS_LINE_LENGTH to configure length.

required
docstring_format

Format of docstring

required
emit_original_whitespace

Whether to emit original whitespace or strip it out (in docstring)

required
emit_default_doc

Whether help/docstring should include 'With default' text

required

Returns:

Name Type Description
return_type

Class AST

Source code in cdd/class_/utils/shared_utils.py
def __call__(
    self,
    intermediate_repr: IntermediateRepr,
    emit_call: bool = False,
    class_name: Optional[str] = None,
    class_bases: Tuple[str] = ("object",),
    decorator_list: Optional[List[str]] = None,
    word_wrap: bool = True,
    docstring_format: Literal["rest", "numpydoc", "google"] = "rest",
    emit_original_whitespace: bool = False,
    emit_default_doc: bool = False,
) -> ClassDef:
    """
    Construct a class

    :param intermediate_repr: a dictionary consistent with `IntermediateRepr`, defined as:
        ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any})
        IntermediateRepr = TypedDict("IntermediateRepr", {
            "name": Optional[str],
            "type": Optional[str],
            "doc": Optional[str],
            "params": OrderedDict[str, ParamVal],
            "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]],
        })

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

    :param class_name: name of class

    :param class_bases: bases of class (the generated class will inherit these)

    :param decorator_list: List of decorators

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

    :param docstring_format: Format of docstring

    :param emit_original_whitespace: Whether to emit original whitespace or strip it out (in docstring)

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

    :return: Class AST
    """

ClassParserProtocol

Bases: Protocol

Protocol for class parser

Parameters:

Name Type Description Default

__call__

__call__(class_def: Union[Module, ClassDef], class_name: Optional[str] = None, merge_inner_function: Optional[str] = None, infer_type: bool = False, parse_original_whitespace: bool = False, word_wrap: bool = True) -> IntermediateRepr

Converts an AST to our IR

Parameters:

Name Type Description Default
class_def

Class AST or Module AST with a ClassDef inside

required
class_name

Name of class. If None, gives first found.

required
merge_inner_function

Name of inner function to merge. If None, merge nothing.

required
infer_type

Whether to try inferring the typ (from the default)

required
parse_original_whitespace

Whether to parse original whitespace or strip it out

required
word_wrap

Whether to word-wrap. Set DOCTRANS_LINE_LENGTH to configure length.

required

Returns:

Name Type Description
return_type

a dictionary consistent with IntermediateRepr, defined as: ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any}) IntermediateRepr = TypedDict("IntermediateRepr", { "name": Optional[str], "type": Optional[str], "doc": Optional[str], "params": OrderedDict[str, ParamVal], "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]], })

Source code in cdd/class_/utils/shared_utils.py
def __call__(
    self,
    class_def: Union[Module, ClassDef],
    class_name: Optional[str] = None,
    merge_inner_function: Optional[str] = None,
    infer_type: bool = False,
    parse_original_whitespace: bool = False,
    word_wrap: bool = True,
) -> IntermediateRepr:
    """
    Converts an AST to our IR

    :param class_def: Class AST or Module AST with a ClassDef inside

    :param class_name: Name of `class`. If None, gives first found.

    :param merge_inner_function: Name of inner function to merge. If None, merge nothing.

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

    :param parse_original_whitespace: Whether to parse original whitespace or strip it out

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

    :return: a dictionary consistent with `IntermediateRepr`, defined as:
        ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any})
        IntermediateRepr = TypedDict("IntermediateRepr", {
            "name": Optional[str],
            "type": Optional[str],
            "doc": Optional[str],
            "params": OrderedDict[str, ParamVal],
            "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]],
        })
    """

cdd.compound

cdd.compound

cdd.compound contains modules that combine multiple others

This is used for OpenAPI, CLI subcommands, and similar.

Parameters:

Name Type Description Default

cdd.compound.doctrans

cdd.compound.doctrans

Helper to traverse the AST of the input file, extract the docstring out, parse and format to intended style, and emit

Parameters:

Name Type Description Default

doctrans

doctrans(filename, docstring_format, type_annotations, no_word_wrap)

Transform the docstrings found within provided filename to intended docstring_format

Parameters:

Name Type Description Default
filename str

Python file to convert docstrings within. Edited in place.

required
docstring_format Literal['rest', 'numpydoc', 'google']

Format of docstring

required
type_annotations bool

True to have type annotations (3.6+), False to place in docstring

required
no_word_wrap Optional[Literal[True]]

Whether word-wrap is disabled (on emission).

required
Source code in cdd/compound/doctrans.py
def doctrans(filename, docstring_format, type_annotations, no_word_wrap):
    """
    Transform the docstrings found within provided filename to intended docstring_format

    :param filename: Python file to convert docstrings within. Edited in place.
    :type filename: ```str```

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

    :param type_annotations: True to have type annotations (3.6+), False to place in docstring
    :type type_annotations: ```bool```

    :param no_word_wrap: Whether word-wrap is disabled (on emission).
    :type no_word_wrap: ```Optional[Literal[True]]```
    """
    with open(filename, "rt") as f:
        original_source: str = f.read()
    node: Module = ast_parse(original_source, skip_docstring_remit=False)
    original_module: Module = deepcopy(node)

    node: Module = fix_missing_locations(
        DocTrans(
            docstring_format=docstring_format,
            word_wrap=no_word_wrap is None,
            type_annotations=type_annotations,
            existing_type_annotations=has_type_annotations(node),
            whole_ast=original_module,
        ).visit(node)
    )

    if not cmp_ast(node, original_module):
        cst_list: List[NamedTuple] = list(cst_parse(original_source))

        # Carefully replace only docstrings, function return annotations, assignment and annotation assignments.
        # Maintaining all other existing whitespace, comments, &etc.
        doctransify_cst(cst_list, node)

        with open(filename, "wt") as f:
            f.write("".join(map(attrgetter("value"), cst_list)))

cdd.compound.doctrans_utils

cdd.compound.doctrans_utils

Helpers to traverse the AST, extract the docstring out, parse and format to intended style

Parameters:

Name Type Description Default

DocTrans

DocTrans(docstring_format, word_wrap, type_annotations, existing_type_annotations, whole_ast)

Bases: NodeTransformer

Walk the nodes modifying the docstring and inlining||commenting types as it goes

Parameters:

Name Type Description Default

Transform the docstrings found to intended docstring_format, potentially manipulating type annotations also

Parameters:

Name Type Description Default
docstring_format Literal['rest', 'numpydoc', 'google']

Format of docstring

required
word_wrap bool

Whether to word-wrap. Set DOCTRANS_LINE_LENGTH to configure length.

required
type_annotations bool

True to have type annotations (3.6+), False to place in docstring

required
existing_type_annotations bool

Whether there are any type annotations (3.6+)

required
whole_ast AST``

The entire input AST, useful for lookups by location

required
Source code in cdd/compound/doctrans_utils.py
def __init__(
    self,
    docstring_format,
    word_wrap,
    type_annotations,
    existing_type_annotations,
    whole_ast,
):
    """
    Transform the docstrings found to intended docstring_format, potentially manipulating type annotations also

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

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

    :param type_annotations: True to have type annotations (3.6+), False to place in docstring
    :type type_annotations: ```bool```

    :param existing_type_annotations: Whether there are any type annotations (3.6+)
    :type existing_type_annotations: ```bool```

    :param whole_ast: The entire input AST, useful for lookups by location
    :type whole_ast: ```AST``
    """

    self.docstring_format = docstring_format
    self.word_wrap = word_wrap
    self.type_annotations = type_annotations
    self.existing_type_annotations = existing_type_annotations
    if not hasattr(whole_ast, "_location"):
        self.whole_ast = deepcopy(whole_ast)
        annotate_ancestry(self.whole_ast)
    else:
        self.whole_ast = whole_ast
    self.memoized = {}

visit_AnnAssign

visit_AnnAssign(node)

Handle AnnAssign

Parameters:

Name Type Description Default
node AnnAssign

AnnAssign

required

Returns:

Name Type Description
return_type Union[AnnAssign, Assign]

AnnAssign if type_annotations and type found else Assign

Source code in cdd/compound/doctrans_utils.py
def visit_AnnAssign(self, node):
    """
    Handle `AnnAssign`

    :param node: AnnAssign
    :type node: ```AnnAssign```

    :return: `AnnAssign` if `type_annotations` and type found else `Assign`
    :rtype: ```Union[AnnAssign, Assign]```
    """
    if self.type_annotations:
        node.annotation = self._get_ass_typ(node)
        setattr(node, "type_comment", None)
        return node

    return Assign(
        targets=[node.target],
        lineno=node.lineno,
        col_offset=getattr(node, "col_offset", None),
        end_lineno=getattr(node, "end_lineno", None),
        end_col_offset=getattr(node, "end_col_offset", None),
        type_comment=to_type_comment(node.annotation),
        # `var: int` is valid and turning it to `var = None  # type_comment int` would
        # be wrong, as the upcoming smarter type tracer will reverse this to `var: Optional[int] = None`
        value=set_value(none_types[-1]) if node.value is None else node.value,
    )

visit_Assign

visit_Assign(node)

Handle Assign

Parameters:

Name Type Description Default
node Assign

Assign

required

Returns:

Name Type Description
return_type Union[Assign, AnnAssign]

AnnAssign if type_annotations and type found else Assign

Source code in cdd/compound/doctrans_utils.py
def visit_Assign(self, node):
    """
    Handle `Assign`

    :param node: Assign
    :type node: ```Assign```

    :return: `AnnAssign` if `type_annotations` and type found else `Assign`
    :rtype: ```Union[Assign, AnnAssign]```
    """
    typ = self._get_ass_typ(node)
    annotation = (
        None if not self.type_annotations or typ is None else to_annotation(typ)
    )
    if annotation:
        assert len(node.targets) == 1
        return AnnAssign(
            annotation=to_annotation(typ),
            lineno=node.lineno,
            col_offset=getattr(node, "col_offset", None),
            end_lineno=getattr(node, "end_lineno", None),
            end_col_offset=getattr(node, "end_col_offset", None),
            simple=1,
            target=node.targets[0],
            expr=None,
            expr_target=None,
            expr_annotation=None,
            value=node.value,
            **maybe_type_comment,
        )
    else:
        setattr(node, "type_comment", typ)
    return node

visit_FunctionDef

visit_FunctionDef(node)

visits the FunctionDef, potentially augmenting its docstring and argument types

Parameters:

Name Type Description Default
node FunctionDef

FunctionDef

required

Returns:

Name Type Description
return_type FunctionDef

Potentially changed FunctionDef

Source code in cdd/compound/doctrans_utils.py
def visit_FunctionDef(self, node):
    """
    visits the `FunctionDef`, potentially augmenting its docstring and argument types

    :param node: FunctionDef
    :type node: ```FunctionDef```

    :return: Potentially changed FunctionDef
    :rtype: ```FunctionDef```
    """
    return self._handle_function(node, get_docstring(node, clean=False))

clear_annotation

clear_annotation(node)

Remove annotations and type_comments from node

Parameters:

Name Type Description Default
node AST

AST node

required

Returns:

Name Type Description
return_type AST

AST node with annotations and type_comments set to None

Source code in cdd/compound/doctrans_utils.py
def clear_annotation(node):
    """
    Remove annotations and type_comments from node

    :param node: AST node
    :type node: ```AST```

    :return: AST node with annotations and type_comments set to `None`
    :rtype: ```AST```
    """
    if getattr(node, "annotation", None) is not None:
        node.annotation = None
    if getattr(node, "type_comment", None) is not None:
        setattr(node, "type_comment", None)
    return node

doctransify_cst

doctransify_cst(cst_list, node)

Carefully replace only docstrings, function return annotations, assignment and annotation assignments. (maintaining all other existing whitespace, comments, &etc.); and only when cdd has changed them

Parameters:

Name Type Description Default
cst_list list[NamedTuple]

List of namedtuples with at least ("line_no_start", "line_no_end", "value") attributes

required
node AST

AST node with a .body, probably the ast.Module

required
Source code in cdd/compound/doctrans_utils.py
def doctransify_cst(cst_list, node):
    """
    Carefully replace only docstrings, function return annotations, assignment and annotation assignments.
    (maintaining all other existing whitespace, comments, &etc.); and only when cdd has changed them

    :param cst_list: List of `namedtuple`s with at least ("line_no_start", "line_no_end", "value") attributes
    :type cst_list: ```list[NamedTuple]```

    :param node: AST node with a `.body`, probably the `ast.Module`
    :type node: ```AST```
    """
    for _node in filter(rpartial(hasattr, "_location"), walk(node)):
        is_func: bool = isinstance(_node, (AsyncFunctionDef, FunctionDef))
        if isinstance(_node, ClassDef) or is_func:
            cst_idx, cst_node = cdd.shared.ast_cst_utils.find_cst_at_ast(
                cst_list, _node
            )

            if cst_node is None:
                continue

            cdd.shared.ast_cst_utils.maybe_replace_doc_str_in_function_or_class(
                _node, cst_idx, cst_list
            )

            if not is_func:
                continue

            cur_ast_node: AST = ast_parse(
                reindent_block_with_pass_body(cst_list[cst_idx].value),
                skip_annotate=True,
                skip_docstring_remit=True,
            ).body[0]

            cdd.shared.ast_cst_utils.maybe_replace_function_return_type(
                _node, cur_ast_node, cst_idx, cst_list
            )
            cdd.shared.ast_cst_utils.maybe_replace_function_args(
                _node, cur_ast_node, cst_idx, cst_list
            )

has_type_annotations

has_type_annotations(node)

Whether the node—incl. any nodes within this node—have type annotations

Parameters:

Name Type Description Default
node AST

AST node

required
Source code in cdd/compound/doctrans_utils.py
def has_type_annotations(node):
    """
    Whether the node—incl. any nodes within this node—have type annotations

    :param node: AST node
    :type node: ```AST```
    """
    return any(
        filter(
            lambda _node: hasattr(_node, "annotation")
            and _node.annotation is not None
            or hasattr(_node, "returns")
            and _node.returns is not None,
            walk(node),
        )
    )

cdd.compound.exmod

cdd.compound.exmod

Not a dead module

Parameters:

Name Type Description Default

exmod

exmod(emit_name, module, blacklist, whitelist, output_directory, target_module_name, mock_imports, emit_sqlalchemy_submodule, extra_modules, no_word_wrap, recursive, dry_run, filesystem_layout='as_input', extra_modules_to_all=None)

Expose module as emit types into output_directory

Parameters:

Name Type Description Default
emit_name list[Literal["argparse", "class", "function", "json_schema", "pydantic", "sqlalchemy", "sqlalchemy_table", "sqlalchemy_hybrid"]]

What type(s) to generate.

required
module str

Module name or path

required
blacklist Union[list[str], tuple[str]]

Modules/FQN to omit. If unspecified will emit all (unless whitelist).

required
whitelist Union[list[str], tuple[str]]

Modules/FQN to emit. If unspecified will emit all (minus blacklist).

required
output_directory str

Where to place the generated exposed interfaces to the given --module.

required
target_module_name Optional[str]

Target module name

required
mock_imports bool

Whether to generate mock TensorFlow imports

required
emit_sqlalchemy_submodule bool

Whether to emit submodule "sqlalchemy_mod/{init,connection,create_tables}.py"

required
extra_modules Optional[List[str]]

Additional module(s) to expose; specifiable multiple times. Prepended to symbol auto-importer

required
no_word_wrap Optional[Literal[True]]

Whether word-wrap is disabled (on emission).

required
recursive bool

Recursively traverse module hierarchy and recreate hierarchy with exposed interfaces

required
dry_run bool

Show what would be created; don't actually write to the filesystem

required
filesystem_layout Literal["java", "as_input"]

Hierarchy of folder and file names generated. "java" is file per package per name.

required
extra_modules_to_all Optional[tuple[tuple[str, frozenset], ...]]

Internal arg. Prepended to symbol resolver. E.g., (("ast", {"List"}),).

required
Source code in cdd/compound/exmod.py
def exmod(
    emit_name,
    module,
    blacklist,
    whitelist,
    output_directory,
    target_module_name,
    mock_imports,
    emit_sqlalchemy_submodule,
    extra_modules,
    no_word_wrap,
    recursive,
    dry_run,
    filesystem_layout="as_input",
    extra_modules_to_all=None,
):
    """
    Expose module as `emit` types into `output_directory`

    :param emit_name: What type(s) to generate.
    :type emit_name: ```list[Literal["argparse", "class", "function", "json_schema",
                                     "pydantic", "sqlalchemy", "sqlalchemy_table", "sqlalchemy_hybrid"]]```

    :param module: Module name or path
    :type module: ```str```

    :param blacklist: Modules/FQN to omit. If unspecified will emit all (unless whitelist).
    :type blacklist: ```Union[list[str], tuple[str]]```

    :param whitelist: Modules/FQN to emit. If unspecified will emit all (minus blacklist).
    :type whitelist: ```Union[list[str], tuple[str]]```

    :param output_directory: Where to place the generated exposed interfaces to the given `--module`.
    :type output_directory: ```str```

    :param target_module_name: Target module name
    :type target_module_name: ```Optional[str]```

    :param mock_imports: Whether to generate mock TensorFlow imports
    :type mock_imports: ```bool```

    :param emit_sqlalchemy_submodule: Whether to emit submodule "sqlalchemy_mod/{__init__,connection,create_tables}.py"
    :type emit_sqlalchemy_submodule: ```bool```

    :param extra_modules: Additional module(s) to expose; specifiable multiple times. Prepended to symbol auto-importer
    :type extra_modules: ```Optional[List[str]]```

    :param no_word_wrap: Whether word-wrap is disabled (on emission).
    :type no_word_wrap: ```Optional[Literal[True]]```

    :param recursive: Recursively traverse module hierarchy and recreate hierarchy with exposed interfaces
    :type recursive: ```bool```

    :param dry_run: Show what would be created; don't actually write to the filesystem
    :type dry_run: ```bool```

    :param filesystem_layout: Hierarchy of folder and file names generated. "java" is file per package per name.
    :type filesystem_layout: ```Literal["java", "as_input"]```

    :param extra_modules_to_all: Internal arg. Prepended to symbol resolver. E.g., `(("ast", {"List"}),)`.
    :type extra_modules_to_all: ```Optional[tuple[tuple[str, frozenset], ...]]```
    """
    output_directory = path.realpath(output_directory)
    extra_modules_to_all = (
        cdd.shared.ast_utils.module_to_all(extra_modules)
        if extra_modules is not None and extra_modules_to_all is None
        else tuple()
    )  # type: tuple[tuple[str, frozenset], ...]
    if not isinstance(emit_name, str):
        deque(
            map(
                partial(
                    exmod,
                    module=module,
                    blacklist=blacklist,
                    whitelist=whitelist,
                    mock_imports=mock_imports,
                    filesystem_layout=filesystem_layout,
                    emit_sqlalchemy_submodule=emit_sqlalchemy_submodule,
                    extra_modules=extra_modules,
                    no_word_wrap=no_word_wrap,
                    output_directory=output_directory,
                    target_module_name=target_module_name,
                    recursive=recursive,
                    dry_run=dry_run,
                    extra_modules_to_all=extra_modules_to_all,
                ),
                emit_name or iter(()),
            ),
            maxlen=0,
        )
    elif dry_run:
        print(
            "mkdir\t'{output_directory}'".format(
                output_directory=path.normcase(output_directory)
            ),
            file=cdd.compound.exmod_utils.EXMOD_OUT_STREAM,
        )
    elif not path.isdir(output_directory):
        makedirs(output_directory)

    emit_name: Optional[str] = (
        emit_name[0]
        if emit_name is not None
        and len(emit_name) == 1
        and isinstance(emit_name, (list, tuple))
        else emit_name
    )
    assert isinstance(
        emit_name, (str, type(None))
    ), "Expected `str` got `{emit_name_type!r}`".format(emit_name_type=type(emit_name))

    module_root, _, submodule = module.rpartition(".")
    module_name, new_module_name = (
        module,
        (
            target_module_name or "___".join((module_root, "gold"))
            if module_root
            else "gold"
        ),
    )

    sqlalchemy_mod: str = "sqlalchemy_mod"
    sqlalchemy_mod_dir_join: typing.Union[
        typing.Callable[[str], str], typing.Callable[[], str]
    ] = partial(path.join, output_directory, "sqlalchemy_mod")
    sqlalchemy_mod_dir = sqlalchemy_mod_dir_join()
    make_sqlalchemy_mod: bool = (
        emit_name in frozenset(("sqlalchemy", "sqlalchemy_hybrid", "sqlalchemy_table"))
        and emit_sqlalchemy_submodule
        and not path.isdir(sqlalchemy_mod_dir)
    )
    if make_sqlalchemy_mod:
        extra_modules_to_all = _create_sqlalchemy_mod(
            extra_modules_to_all,
            output_directory,
            sqlalchemy_mod,
            sqlalchemy_mod_dir,
            sqlalchemy_mod_dir_join,
        )
    try:
        module_root_dir: str = path.dirname(
            find_module_filepath(
                *(module_root, submodule) if module_root else (module_name, None)
            )
        )
    except AssertionError as e:
        raise ModuleNotFoundError(e)

    _exmod_single_folder = partial(
        exmod_single_folder,
        emit_name=emit_name,
        blacklist=blacklist,
        whitelist=whitelist,
        mock_imports=mock_imports,
        no_word_wrap=no_word_wrap,
        dry_run=dry_run,
        module_root=module_root,
        new_module_name=new_module_name,
        filesystem_layout=filesystem_layout,
        extra_modules_to_all=extra_modules_to_all,
        first_output_directory=output_directory,
    )
    packages: typing.List[str] = find_packages(
        module_root_dir,
        include=whitelist if whitelist else ("*",),
        exclude=blacklist if blacklist else iter(()),
    )

    _exmod_single_folder(
        module=module,
        module_name=module_name,
        module_root_dir=module_root_dir,
        output_directory=output_directory,
    )
    output_directory_basename = path.basename(output_directory)
    imports = (
        [output_directory_basename] if make_sqlalchemy_mod else None
    )  # type: Optional[list[str]]
    _exmod_single_folder_kwargs: Tuple[
        TypedDict(
            "_exmod_single_folder_kwargs",
            {
                "module": module,
                "module_name": module_name,
                "module_root_dir": module_root_dir,
                "output_directory": output_directory,
            },
        )
    ] = tuple(
        chain.from_iterable(
            (
                (
                    {
                        "module": module,
                        "module_name": module_name,
                        "module_root_dir": module_root_dir,
                        "output_directory": output_directory,
                    },
                ),
                (
                    map(
                        lambda package: (
                            lambda pkg_relative_dir: (
                                imports is not None
                                and imports.append(
                                    path.join(
                                        output_directory_basename,
                                        pkg_relative_dir,
                                    ).replace(path.sep, ".")
                                )
                                or {
                                    "module": ".".join((module, package)),
                                    "module_name": package,
                                    "module_root_dir": path.join(
                                        module_root_dir, pkg_relative_dir
                                    ),
                                    "output_directory": path.join(
                                        output_directory, pkg_relative_dir
                                    ),
                                }
                            )
                        )(package.replace(".", path.sep)),
                        packages,
                    )
                    if recursive
                    else iter(())
                ),
            )
        )
    )

    if make_sqlalchemy_mod:
        _add_imports_to_sqlalchemy_create_all(imports, sqlalchemy_mod_dir_join)

    # This could be executed in parallel for efficiency
    deque(
        map(
            lambda kwargs: _exmod_single_folder(**kwargs),
            _exmod_single_folder_kwargs,
        ),
        maxlen=0,
    )

    return

exmod_single_folder

exmod_single_folder(emit_name, module, blacklist, whitelist, output_directory, first_output_directory, mock_imports, no_word_wrap, dry_run, module_root_dir, module_root, module_name, new_module_name, filesystem_layout, extra_modules_to_all)

Expose module as emit types into output_directory. Single folder (non-recursive).

Parameters:

Name Type Description Default
emit_name list[Literal["argparse", "class", "function", "json_schema", "pydantic", "sqlalchemy", "sqlalchemy_table", "sqlalchemy_hybrid"]]

What type(s) to generate.

required
module str

Module name or path

required
blacklist Union[list[str], tuple[str]]

Modules/FQN to omit. If unspecified will emit all (unless whitelist).

required
whitelist Union[list[str], tuple[str]]

Modules/FQN to emit. If unspecified will emit all (minus blacklist).

required
output_directory str

Where to place the generated exposed interfaces to the given --module.

required
first_output_directory str

Initial output directory (e.g., direct from --output-directory)

required
mock_imports bool

Whether to generate mock TensorFlow imports

required
no_word_wrap Optional[Literal[True]]

Whether word-wrap is disabled (on emission).

required
dry_run bool

Show what would be created; don't actually write to the filesystem

required
module_root_dir str
required
module_root str
required
module_name str
required
new_module_name str
required
filesystem_layout Literal["java", "as_input"]

Hierarchy of folder and file names generated. "java" is file per package per name.

required
extra_modules_to_all Optional[tuple[tuple[str, frozenset], ...]]

Internal arg. Prepended to symbol resolver. E.g., (("ast", {"List"}),).

required
Source code in cdd/compound/exmod.py
def exmod_single_folder(
    emit_name,
    module,
    blacklist,
    whitelist,
    output_directory,
    first_output_directory,
    mock_imports,
    no_word_wrap,
    dry_run,
    module_root_dir,
    module_root,
    module_name,
    new_module_name,
    filesystem_layout,
    extra_modules_to_all,
):
    """
    Expose module as `emit` types into `output_directory`. Single folder (non-recursive).

    :param emit_name: What type(s) to generate.
    :type emit_name: ```list[Literal["argparse", "class", "function", "json_schema",
                                     "pydantic", "sqlalchemy", "sqlalchemy_table", "sqlalchemy_hybrid"]]```

    :param module: Module name or path
    :type module: ```str```

    :param blacklist: Modules/FQN to omit. If unspecified will emit all (unless whitelist).
    :type blacklist: ```Union[list[str], tuple[str]]```

    :param whitelist: Modules/FQN to emit. If unspecified will emit all (minus blacklist).
    :type whitelist: ```Union[list[str], tuple[str]]```

    :param output_directory: Where to place the generated exposed interfaces to the given `--module`.
    :type output_directory: ```str```

    :param first_output_directory: Initial output directory (e.g., direct from `--output-directory`)
    :type first_output_directory: ```str```

    :param mock_imports: Whether to generate mock TensorFlow imports
    :type mock_imports: ```bool```

    :param no_word_wrap: Whether word-wrap is disabled (on emission).
    :type no_word_wrap: ```Optional[Literal[True]]```

    :param dry_run: Show what would be created; don't actually write to the filesystem
    :type dry_run: ```bool```

    :param module_root_dir:
    :type module_root_dir: ```str```

    :param module_root:
    :type module_root: ```str```

    :param module_name:
    :type module_name: ```str```

    :param new_module_name:
    :type new_module_name: ```str```

    :param filesystem_layout: Hierarchy of folder and file names generated. "java" is file per package per name.
    :type filesystem_layout: ```Literal["java", "as_input"]```

    :param extra_modules_to_all: Internal arg. Prepended to symbol resolver. E.g., `(("ast", {"List"}),)`.
    :type extra_modules_to_all: ```Optional[tuple[tuple[str, frozenset], ...]]```
    """
    mod_path: str = (
        module_name
        if module_name.startswith(module_root + ".")
        else ".".join((module_root, module_name))
    )
    blacklist, whitelist = map(
        frozenset, (blacklist or iter(()), whitelist or iter(()))
    )
    proceed: bool = any(
        (
            sum(map(len, (blacklist, whitelist))) == 0,
            mod_path not in blacklist and (mod_path in whitelist or not whitelist),
        )
    )
    if not proceed:
        return

    _emit_files_from_module_and_return_imports = partial(
        cdd.compound.exmod_utils.emit_files_from_module_and_return_imports,
        new_module_name=new_module_name,
        emit_name=emit_name,
        output_directory=output_directory,
        first_output_directory=first_output_directory,
        mock_imports=mock_imports,
        no_word_wrap=no_word_wrap,
        dry_run=dry_run,
        filesystem_layout=filesystem_layout,
        extra_modules_to_all=extra_modules_to_all,
    )

    imports = _emit_files_from_module_and_return_imports(
        module_name=module_name, module=module, module_root_dir=module_root_dir
    )  # type: Optional[list[ImportFrom]]
    if not imports:
        # Case: no obvious folder hierarchy, so parse the `__init__` file in root
        top_level_init = path.join(module_root_dir, INIT_FILENAME)
        with open(top_level_init, "rt") as f:
            mod: Module = parse(f.read(), filename=top_level_init)

        # TODO: Optimise these imports
        imports = list(
            chain.from_iterable(
                map(
                    lambda filepath_name_module: _emit_files_from_module_and_return_imports(
                        module_root_dir=filepath_name_module[0],
                        module_name=filepath_name_module[1],
                        module=filepath_name_module[2],
                    ),
                    map(
                        lambda filepath2modname_group: (
                            filepath2modname_group[0][0],
                            filepath2modname_group[0][1],
                            reduce(
                                partial(merge_modules, deduplicate_names=True),
                                map(itemgetter(1), filepath2modname_group[1]),
                            ),
                        ),
                        groupby(
                            sorted(
                                map(
                                    lambda import_from: (
                                        (
                                            lambda module_filepath: (
                                                (module_filepath, import_from.module),
                                                construct_module_with_symbols(
                                                    parse(
                                                        read_file_to_str(
                                                            module_filepath
                                                        )
                                                    ),
                                                    map(
                                                        attrgetter("name"),
                                                        import_from.names,
                                                    ),
                                                ),
                                            )
                                        )(
                                            find_module_filepath(
                                                *import_from.module.rsplit(".", 1)
                                            )
                                        )
                                    ),
                                    filter(rpartial(isinstance, ImportFrom), mod.body),
                                ),
                                key=itemgetter(0),
                            ),
                            key=itemgetter(0),
                        ),
                    ),
                )
            )
        )  # type: list[ImportFrom]

    # assert imports, "Module contents are empty at {!r}".format(module_root_dir)
    modules_names: Tuple[str, ...] = cast(
        Tuple[str, ...],
        tuple(
            map(
                lambda name_module: (
                    name_module[0],
                    tuple(map(itemgetter(1), name_module[1])),
                ),
                groupby(
                    map(
                        lambda node_mod: (
                            node_mod[0],
                            node_mod[2].module,
                        ),
                        imports,
                    ),
                    itemgetter(0),
                ),
            )
        ),
    )

    init_filepath: str = path.join(
        output_directory,
        *(
            (INIT_FILENAME,)
            if output_directory.endswith(
                "{}{}".format(path.sep, new_module_name.replace(".", path.sep))
            )
            else (new_module_name, INIT_FILENAME)
        )
    )
    if dry_run:
        print(
            "write\t'{init_filepath}'".format(
                init_filepath=path.normcase(init_filepath)
            ),
            file=cdd.compound.exmod_utils.EXMOD_OUT_STREAM,
        )
    else:
        makedirs(path.dirname(init_filepath), exist_ok=True)
        cdd.shared.emit.file.file(
            deduplicate_sorted_imports(
                Module(
                    body=list(
                        chain.from_iterable(
                            (
                                (
                                    Expr(
                                        set_value("\nExport internal imports\n"),
                                        lineno=None,
                                        col_offset=None,
                                    ),
                                ),
                                map(
                                    lambda module_names: ImportFrom(
                                        module=module_names[0],
                                        names=list(
                                            map(
                                                lambda names: alias(
                                                    names,
                                                    None,
                                                    identifier=None,
                                                    identifier_name=None,
                                                ),
                                                module_names[1],
                                            )
                                        ),
                                        level=1,
                                        identifier=None,
                                    ),
                                    modules_names,
                                ),
                                (
                                    Assign(
                                        targets=[
                                            Name(
                                                "__all__",
                                                Store(),
                                                lineno=None,
                                                col_offset=None,
                                            )
                                        ],
                                        value=List(
                                            ctx=Load(),
                                            elts=list(
                                                map(
                                                    set_value,
                                                    sorted(
                                                        frozenset(
                                                            chain.from_iterable(
                                                                map(
                                                                    itemgetter(1),
                                                                    modules_names,
                                                                )
                                                            ),
                                                        )
                                                    ),
                                                )
                                            ),
                                            expr=None,
                                        ),
                                        expr=None,
                                        lineno=None,
                                        **maybe_type_comment
                                    ),
                                ),
                            )
                        )
                    ),
                    stmt=None,
                    type_ignores=[],
                )
            ),
            init_filepath,
            mode="wt",
        )

cdd.compound.exmod_utils

cdd.compound.exmod_utils

Exmod utils

Parameters:

Name Type Description Default

emit_file_on_hierarchy

emit_file_on_hierarchy(name_orig_ir, emit_name, module_name, new_module_name, mock_imports, filesystem_layout, extra_modules_to_all, output_directory, first_output_directory, no_word_wrap, dry_run)

Generate Java-package—or match input—style file hierarchy from fully-qualified module name

Parameters:

Name Type Description Default
name_orig_ir str

FQ module name, original filename path, IR

required
emit_name list[Literal["argparse", "class", "function", "json_schema", "pydantic", "sqlalchemy", "sqlalchemy_table", "sqlalchemy_hybrid"]]

What type(s) to generate.

required
module_name str

Name of [original] module

required
new_module_name str

Name of [new] module

required
mock_imports bool

Whether to generate mock TensorFlow imports

required
filesystem_layout Literal["java", "as_input"]

Hierarchy of folder and file names generated. "java" is file per package per name.

required
extra_modules_to_all Optional[tuple[tuple[str, frozenset], ...]]

Internal arg. Prepended to symbol resolver. E.g., (("ast", {"List"}),).

required
output_directory str

Where to place the generated exposed interfaces to the given --module.

required
first_output_directory str

Initial output directory (e.g., direct from --output-directory)

required
no_word_wrap Optional[Literal[True]]

Whether word-wrap is disabled (on emission).

required
dry_run bool

Show what would be created; don't actually write to the filesystem

required

Returns:

Name Type Description
return_type Optional[Tuple[Optional[str], str, ImportFrom]]

(mod_name or None, relative_filename_path, ImportFrom) to generated module

Source code in cdd/compound/exmod_utils.py
def emit_file_on_hierarchy(
    name_orig_ir,
    emit_name,
    module_name,
    new_module_name,
    mock_imports,
    filesystem_layout,
    extra_modules_to_all,
    output_directory,
    first_output_directory,
    no_word_wrap,
    dry_run,
):
    """
    Generate Java-package—or match input—style file hierarchy from fully-qualified module name

    :param name_orig_ir: FQ module name, original filename path, IR
    :type name_orig_ir: ```tuple[str, str, dict]```

    :param emit_name: What type(s) to generate.
    :type emit_name: ```list[Literal["argparse", "class", "function", "json_schema",
                                     "pydantic", "sqlalchemy", "sqlalchemy_table", "sqlalchemy_hybrid"]]```

    :param module_name: Name of [original] module
    :type module_name: ```str```

    :param new_module_name: Name of [new] module
    :type new_module_name: ```str```

    :param mock_imports: Whether to generate mock TensorFlow imports
    :type mock_imports: ```bool```

    :param filesystem_layout: Hierarchy of folder and file names generated. "java" is file per package per name.
    :type filesystem_layout: ```Literal["java", "as_input"]```

    :param extra_modules_to_all: Internal arg. Prepended to symbol resolver. E.g., `(("ast", {"List"}),)`.
    :type extra_modules_to_all: ```Optional[tuple[tuple[str, frozenset], ...]]```

    :param output_directory: Where to place the generated exposed interfaces to the given `--module`.
    :type output_directory: ```str```

    :param first_output_directory: Initial output directory (e.g., direct from `--output-directory`)
    :type first_output_directory: ```str```

    :param no_word_wrap: Whether word-wrap is disabled (on emission).
    :type no_word_wrap: ```Optional[Literal[True]]```

    :param dry_run: Show what would be created; don't actually write to the filesystem
    :type dry_run: ```bool```

    :return: (mod_name or None, relative_filename_path, ImportFrom) to generated module
    :rtype: ```Optional[Tuple[Optional[str], str, ImportFrom]]```
    """
    mod_name, _, name = name_orig_ir[0].rpartition(".")
    original_relative_filename_path, ir = name_orig_ir[1], name_orig_ir[2]
    assert original_relative_filename_path

    relative_filename_path: str = original_relative_filename_path
    module_name_as_path: str = module_name.replace(".", path.sep)
    if relative_filename_path.startswith(module_name_as_path + path.sep):
        relative_filename_path: str = relative_filename_path[
            len(module_name_as_path + path.sep) :
        ]
    if not name and ir.get("name") is not None:
        name: Optional[str] = ir.get("name")

    output_dir_is_module: bool = output_directory.replace(path.sep, ".").endswith(
        new_module_name
    )
    mod_path: str = path.join(
        output_directory,
        *(
            ()
            if output_dir_is_module
            else (new_module_name, mod_name.replace(".", path.sep))
        )
    )
    # print("mkdir\t{mod_path!r}".format(mod_path=mod_path), file=EXMOD_OUT_STREAM)
    if not path.isdir(mod_path):
        if dry_run:
            print(
                "mkdir\t'{mod_path}'".format(mod_path=path.normcase(mod_path)),
                file=EXMOD_OUT_STREAM,
            )
        else:
            makedirs(mod_path)

    init_filepath: str = path.join(path.dirname(mod_path), INIT_FILENAME)
    if dry_run:
        print(
            "touch\t'{init_filepath}'".format(
                init_filepath=path.normcase(init_filepath)
            ),
            file=EXMOD_OUT_STREAM,
        )
    else:
        open(init_filepath, "a").close()

    emit_filename, init_filepath = (
        map(
            partial(
                path.join,
                output_directory,
                *() if output_dir_is_module else (new_module_name,)
            ),
            (
                relative_filename_path,
                path.join(
                    path.dirname(relative_filename_path),
                    INIT_FILENAME,
                ),
            ),
        )
        if filesystem_layout == "as_input"
        else map(
            partial(path.join, mod_path),
            (
                "{name}{extsep}py".format(name=name, extsep=extsep),
                INIT_FILENAME,
            ),
        )
    )
    symbol_in_file: bool = path.isfile(emit_filename)
    isfile_emit_filename: bool = symbol_in_file
    existent_mod: Optional[Module] = None
    if isfile_emit_filename:
        with open(emit_filename, "rt") as f:
            emit_filename_contents: str = f.read()
        existent_mod: Module = ast_parse(
            emit_filename_contents, skip_docstring_remit=True, filename=emit_filename
        )  # Also, useful as this catches syntax errors
        symbol_in_file: bool = any(
            filter(
                partial(eq, name),
                map(
                    attrgetter("name"),
                    filter(rpartial(hasattr, "name"), existent_mod.body),
                ),
            )
        )
    else:
        emit_filename_dir: str = path.dirname(emit_filename)
        if not path.isdir(emit_filename_dir):
            (
                print(
                    "mkdir\t'{emit_filename_dir}'".format(
                        emit_filename_dir=path.normcase(emit_filename_dir)
                    ),
                    file=EXMOD_OUT_STREAM,
                )
                if dry_run
                else makedirs(emit_filename_dir)
            )

    if not symbol_in_file and (ir.get("name") or ir["params"] or ir["returns"]):
        _emit_symbol(
            name_orig_ir=name_orig_ir,
            emit_name=emit_name,
            module_name=module_name,
            emit_filename=emit_filename,
            existent_mod=existent_mod,
            init_filepath=init_filepath,
            intermediate_repr=ir,
            isfile_emit_filename=isfile_emit_filename,
            name=name,
            mock_imports=mock_imports,
            extra_modules_to_all=extra_modules_to_all,
            no_word_wrap=no_word_wrap,
            first_output_directory=first_output_directory,
            dry_run=dry_run,
        )

emit_files_from_module_and_return_imports

emit_files_from_module_and_return_imports(module_name, module_root_dir, new_module_name, emit_name, module, output_directory, first_output_directory, mock_imports, no_word_wrap, dry_run, filesystem_layout, extra_modules_to_all)

Emit type emit_name of all files in module_root_dir into output_directory on new_module_name hierarchy. Then return the new imports.

Parameters:

Name Type Description Default
module_name str

Name of existing module

required
module_root_dir str

Root dir of existing module

required
new_module_name str

New module name

required
emit_name list[Literal["argparse", "class", "function", "json_schema", "pydantic", "sqlalchemy", "sqlalchemy_table", "sqlalchemy_hybrid"]]

What type(s) to generate.

required
module Module

Module itself

required
output_directory str

Where to place the generated exposed interfaces to the given --module.

required
first_output_directory str

Initial output directory (e.g., direct from --output-directory)

required
mock_imports bool

Whether to generate mock TensorFlow imports

required
no_word_wrap Optional[Literal[True]]

Whether word-wrap is disabled (on emission).

required
dry_run bool

Show what would be created; don't actually write to the filesystem

required
filesystem_layout Literal["java", "as_input"]

Hierarchy of folder and file names generated. "java" is file per package per name.

required
extra_modules_to_all Optional[tuple[tuple[str, frozenset], ...]]

Internal arg. Prepended to symbol resolver. E.g., (("ast", {"List"}),).

required

Returns:

Name Type Description
return_type list[Tuple[Optional[str], str, ImportFrom]]

List of (mod_name or None, relative_filename_path, ImportFrom) to generated module(s)

Source code in cdd/compound/exmod_utils.py
def emit_files_from_module_and_return_imports(
    module_name,
    module_root_dir,
    new_module_name,
    emit_name,
    module,
    output_directory,
    first_output_directory,
    mock_imports,
    no_word_wrap,
    dry_run,
    filesystem_layout,
    extra_modules_to_all,
):
    """
    Emit type `emit_name` of all files in `module_root_dir` into `output_directory`
    on `new_module_name` hierarchy. Then return the new imports.

    :param module_name: Name of existing module
    :type module_name: ```str```

    :param module_root_dir: Root dir of existing module
    :type module_root_dir: ```str```

    :param new_module_name: New module name
    :type new_module_name: ```str```

    :param emit_name: What type(s) to generate.
    :type emit_name: ```list[Literal["argparse", "class", "function", "json_schema",
                                     "pydantic", "sqlalchemy", "sqlalchemy_table", "sqlalchemy_hybrid"]]```

    :param module: Module itself
    :type module: ```Module```

    :param output_directory: Where to place the generated exposed interfaces to the given `--module`.
    :type output_directory: ```str```

    :param first_output_directory: Initial output directory (e.g., direct from `--output-directory`)
    :type first_output_directory: ```str```

    :param mock_imports: Whether to generate mock TensorFlow imports
    :type mock_imports: ```bool```

    :param no_word_wrap: Whether word-wrap is disabled (on emission).
    :type no_word_wrap: ```Optional[Literal[True]]```

    :param dry_run: Show what would be created; don't actually write to the filesystem
    :type dry_run: ```bool```

    :param filesystem_layout: Hierarchy of folder and file names generated. "java" is file per package per name.
    :type filesystem_layout: ```Literal["java", "as_input"]```

    :param extra_modules_to_all: Internal arg. Prepended to symbol resolver. E.g., `(("ast", {"List"}),)`.
    :type extra_modules_to_all: ```Optional[tuple[tuple[str, frozenset], ...]]```

    :return: List of (mod_name or None, relative_filename_path, ImportFrom) to generated module(s)
    :rtype: ```list[Tuple[Optional[str], str, ImportFrom]]```
    """
    _emit_file_on_hierarchy = partial(
        emit_file_on_hierarchy,
        emit_name=emit_name,
        module_name=module_name,
        new_module_name=new_module_name,
        mock_imports=mock_imports,
        filesystem_layout=filesystem_layout,
        output_directory=output_directory,
        first_output_directory=first_output_directory,
        extra_modules_to_all=extra_modules_to_all,
        no_word_wrap=no_word_wrap,
        dry_run=dry_run,
    )

    # Might need some `groupby` in case multiple files are in the one project; same for `get_module_contents`
    return list(
        filter(
            None,
            map(
                _emit_file_on_hierarchy,
                map(
                    lambda name_source: (
                        name_source[0],
                        (
                            path.join(output_directory, path.basename(module_root_dir))
                            if path.isfile(module_root_dir)
                            else (
                                lambda filename: (
                                    filename[len(module_name) + 1 :]
                                    if filename.startswith(module_name)
                                    else filename
                                )
                            )(
                                relative_filename(
                                    name_source[1].__file__
                                    if hasattr(name_source[1], "__file__")
                                    else getfile(name_source[1])
                                )
                            )
                        ),
                        (
                            {"params": OrderedDict(), "returns": OrderedDict()}
                            if dry_run
                            else (
                                lambda parser: (
                                    partial(parser, merge_inner_function="__init__")
                                    if parser is cdd.class_.parse.class_
                                    else parser
                                )
                            )(get_parser(name_source[1], "infer"))(name_source[1])
                        ),
                    ),
                    map(
                        lambda name_source: (
                            (
                                name_source[0][len(module_name) + 1 :]
                                if name_source[0].startswith(module_name)
                                else name_source[0]
                            ),
                            name_source[1],
                        ),
                        get_module_contents(
                            module, module_root_dir=module_root_dir
                        ).items(),
                    ),
                ),
            ),
        )
    )

get_module_contents

get_module_contents(obj, module_root_dir, current_module=None, _result={})

Helper function to get the recursive inner module contents

Parameters:

Name Type Description Default
obj Any

Something to dir on

required
module_root_dir str

Root of module

required
current_module Optional[str]

The current module

required
_result dict

The result var (used internally as accumulator)

required

Returns:

Name Type Description
return_type Dict[str,Generator[Any]]

fully-qualified module name to values (could be modules, classes, and whatever other symbols are exposed)

Source code in cdd/compound/exmod_utils.py
def get_module_contents(obj, module_root_dir, current_module=None, _result={}):
    """
    Helper function to get the recursive inner module contents

    :param obj: Something to `dir` on
    :type obj: ```Any```

    :param module_root_dir: Root of module
    :type module_root_dir: ```str```

    :param current_module: The current module
    :type current_module: ```Optional[str]```

    :param _result: The result var (used internally as accumulator)
    :type _result: ```dict```

    :return: fully-qualified module name to values (could be modules, classes, and whatever other symbols are exposed)
    :rtype: ```Dict[str,Generator[Any]]```
    """
    module_root_dir_init: str = path.join(module_root_dir, INIT_FILENAME)
    # process_module_contents = partial(
    #     _process_module_contents,
    #     _result=_result,
    #     current_module=current_module,
    #     module_root_dir=module_root_dir,
    # )
    if path.isfile(module_root_dir):
        with open(module_root_dir, "rt") as f:
            mod: Module = ast_parse(
                f.read(), filename=module_root_dir, skip_docstring_remit=True
            )

        # Bring in imported symbols that should be exposed based on `__all__`
        all_magic_var = next(
            map(
                lambda assign: frozenset(
                    map(cdd.shared.ast_utils.get_value, assign.value.elts)
                ),
                filter(
                    lambda assign: len(assign.targets) == 1
                    and isinstance(assign.targets[0], Name)
                    and assign.targets[0].id == "__all__",
                    filter(rpartial(isinstance, Assign), mod.body),
                ),
            ),
            iter(()),
        )  # type: Union[list[str], Iterator]
        mod_to_symbol: defaultdict[Any, list] = defaultdict(list)
        deque(
            (
                mod_to_symbol[import_from.module].append(name.name)
                for import_from in filter(
                    rpartial(isinstance, ImportFrom), ast_walk(mod)
                )
                for name in import_from.names
                if name.asname is None
                and name.name in all_magic_var
                or name.asname in all_magic_var
            ),
            maxlen=0,
        )
        res: Dict[str, AST] = {
            "{module_name}{submodule_name}.{node_name}".format(
                module_name="{}.".format(module_name) if module_name else "",
                submodule_name=submodule_name,
                node_name=node.name,
            ): node
            for module_name, submodule_names in mod_to_symbol.items()
            for submodule_name in submodule_names
            for node in (
                lambda module_filepath: (
                    iter(())
                    if module_filepath is None
                    else ast_parse(
                        read_file_to_str(module_filepath),
                        module_filepath,
                        skip_docstring_remit=True,
                    ).body
                )
            )(
                cdd.shared.pure_utils.find_module_filepath(
                    module_name, submodule_name, none_when_no_spec=True
                )
            )
            if hasattr(node, "name")
        }
        res.update(
            dict(
                map(
                    lambda node: (
                        (
                            node.name
                            if current_module is None
                            else "{current_module}.{name}".format(
                                current_module=current_module, name=node.name
                            )
                        ),
                        node,
                    ),
                    filter(lambda node: hasattr(node, "name"), mod.body),
                )
            )
        )
        return res
    elif path.isfile(module_root_dir_init):
        return get_module_contents(
            obj=obj,
            module_root_dir=module_root_dir_init,
            current_module=current_module,
            _result=_result,
        )
    # assert not isinstance(
    #     obj, (int, float, complex, str, bool, type(None))
    # ), "module is unexpected type: {!r}".format(type(obj).__name__)
    # for name, symbol in no_magic_or_builtin_dir2attr(obj).items():
    #     process_module_contents(name=name, symbol=symbol)
    return _result

cdd.compound.gen

cdd.compound.gen

Functionality to generate classes, functions, and/or argparse functions from the input mapping

Parameters:

Name Type Description Default

gen

gen(name_tpl, input_mapping, parse_name, emit_name, output_filename, prepend=None, imports_from_file=None, emit_call=False, emit_default_doc=True, emit_and_infer_imports=False, decorator_list=None, phase=0, no_word_wrap=None)

Generate classes, functions, and/or argparse functions from the input mapping

Parameters:

Name Type Description Default
name_tpl str

Template for the name, e.g., {name}Config.

required
input_mapping str

Fully-qualified module, filepath, or directory.

required
parse_name Literal["argparse", "class", "function", "json_schema", "pydantic", "sqlalchemy", "sqlalchemy_table", "sqlalchemy_hybrid", "infer"]

Which type to parse.

required
emit_name Literal["argparse", "class", "function", "json_schema", "pydantic", "sqlalchemy", "sqlalchemy_table", "sqlalchemy_hybrid"]

Which type to generate.

required
output_filename str

Output file to write to

required
prepend Optional[str]

Prepend file with this. Use ' ' for newlines.

required
imports_from_file Optional[str]

Extract imports from file and append to output_file. If module or other symbol path given, resolve file then use it.

required
emit_call bool

Whether to emit a __call__ method from the _internal IR subdict

required
emit_and_infer_imports bool

Whether to emit and infer imports at the top of the generated code

required
emit_default_doc bool

Whether help/docstring should include 'With default' text

required
decorator_list Optional[Union[List[str], List[]]]

List of decorators

required
phase int

Which phase to run through. E.g., SQLalchemy may require multiple phases to resolve foreign keys

required
no_word_wrap Optional[Literal[True]]

Whether word-wrap is disabled (on emission).

required
Source code in cdd/compound/gen.py
def gen(
    name_tpl,
    input_mapping,
    parse_name,
    emit_name,
    output_filename,
    prepend=None,
    imports_from_file=None,
    emit_call=False,
    emit_default_doc=True,
    emit_and_infer_imports=False,
    decorator_list=None,
    phase=0,
    no_word_wrap=None,
):
    """
    Generate classes, functions, and/or argparse functions from the input mapping

    :param name_tpl: Template for the name, e.g., `{name}Config`.
    :type name_tpl: ```str```

    :param input_mapping: Fully-qualified module, filepath, or directory.
    :type input_mapping: ```str```

    :param parse_name: Which type to parse.
    :type parse_name: ```Literal["argparse", "class", "function", "json_schema",
                                 "pydantic", "sqlalchemy", "sqlalchemy_table", "sqlalchemy_hybrid", "infer"]```

    :param emit_name: Which type to generate.
    :type emit_name: ```Literal["argparse", "class", "function", "json_schema",
                                "pydantic", "sqlalchemy", "sqlalchemy_table", "sqlalchemy_hybrid"]```

    :param output_filename: Output file to write to
    :type output_filename: ```str```

    :param prepend: Prepend file with this. Use '\n' for newlines.
    :type prepend: ```Optional[str]```

    :param imports_from_file: Extract imports from file and append to `output_file`.
        If module or other symbol path given, resolve file then use it.
    :type imports_from_file: ```Optional[str]```

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

    :param emit_and_infer_imports: Whether to emit and infer imports at the top of the generated code
    :type emit_and_infer_imports: ```bool```

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

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

    :param phase: Which phase to run through. E.g., SQLalchemy may require multiple phases to resolve foreign keys
    :type phase: ```int```

    :param no_word_wrap: Whether word-wrap is disabled (on emission).
    :type no_word_wrap: ```Optional[Literal[True]]```
    """
    extra_symbols = {}
    if phase > 0 and emit_name in frozenset(
        ("sqlalchemy", "sqlalchemy_hybrid", "sqlalchemy_table")
    ):
        if phase == 1:
            return cdd.sqlalchemy.utils.emit_utils.update_with_imports_from_columns(
                output_filename
            )
        elif phase == 2:
            return cdd.sqlalchemy.utils.emit_utils.update_fk_for_file(output_filename)
        else:
            raise NotImplementedError("phase {}".format(phase))
    elif imports_from_file is None:
        imports: str = ""
    else:
        if prepend:
            prepend_imports: Union[Import, ImportFrom] = get_at_root(
                ast.parse(prepend.strip()), (Import, ImportFrom)
            )

            # def rewrite_typings(node):
            #     """
            #     Python < 3.8 must use `typings_extensions` for `Literal`
            #
            #     :param node: import node
            #     :type node: ```Union[Import, ImportFrom]```
            #
            #     :return: The import potentially rewritten or None
            #     :rtype: ```Optional[Union[Import, ImportFrom]]```
            #     """
            #     if isinstance(node, ImportFrom) and node.module == "typing":
            #         len_names = len(node.names)
            #         if len_names == 1 and node.names[0].name == "Literal":
            #             rewrite_typings.found_literal = True
            #             return None
            #         else:
            #             node.names = list(
            #                 filter(
            #                     None,
            #                     map(
            #                         lambda _alias: None
            #                         if _alias.name == "Literal"
            #                         else _alias,
            #                         node.names,
            #                     ),
            #                 )
            #             )
            #             if len(node.names) != len_names:
            #                 rewrite_typings.found_literal = True
            #     return node
            #
            # rewrite_typings.found_literal = False
            # prepend_imports = list(filter(None, map(rewrite_typings, prepend_imports)))
            # if rewrite_typings.found_literal:
            #     prepend_imports.append(
            #         ImportFrom(
            #             level=0,
            #             module="typing_extensions"
            #             if sys.version_info[:2] < (3, 8)
            #             else "typing",
            #             names=[alias(asname=None, name="Literal")],
            #             lineno=None,
            #             col_offset=None,
            #         )
            #     )

            eval(
                compile(
                    to_code(
                        ast.fix_missing_locations(
                            Module(body=prepend_imports, stmt=None, type_ignores=[])
                        )
                    ),
                    filename="<string>",
                    mode="exec",
                ),
                extra_symbols,
            )
            # This leaks to the global scope
            globals().update(extra_symbols)
        with open(
            (
                imports_from_file
                if path.isfile(imports_from_file)
                else getfile(get_module(imports_from_file, extra_symbols=extra_symbols))
            ),
            "rt",
        ) as f:
            imports: str = "".join(
                map(to_code, get_at_root(ast.parse(f.read()), (Import, ImportFrom)))
            )

    module_path, _, symbol_name = input_mapping.rpartition(".")

    emit_name: str = sanitise_emit_name(emit_name)
    if path.isfile(input_mapping):
        input_mapping = file_to_input_mapping(input_mapping, parse_name)
    elif path.isdir(input_mapping):
        _input_mapping = {}
        deque(
            map(
                _input_mapping.update,
                map(
                    partial(
                        file_to_input_mapping,
                        parse_name=parse_name,
                    ),
                    map(partial(path.join, input_mapping), listdir(input_mapping)),
                ),
            ),
            maxlen=0,
        )
        input_mapping = _input_mapping
        del _input_mapping
    else:
        input_mod = get_module(module_path, extra_symbols=extra_symbols)
        input_mapping = (
            getattr(input_mod, symbol_name)
            if hasattr(input_mod, symbol_name)
            else get_input_mapping_from_path(emit_name, module_path, symbol_name)
        )
    input_mapping_it = (
        input_mapping.items() if hasattr(input_mapping, "items") else input_mapping
    )

    return (
        cdd.json_schema.emit.json_schema_file(
            {
                name: get_emitter(emit_name)(
                    get_parser(node, parse_name)(node),
                    emit_default_doc=emit_default_doc,
                    word_wrap=no_word_wrap is None,
                    **get_emit_kwarg(
                        decorator_list, emit_call, emit_name, name_tpl, name
                    ),
                )
                for name, node in input_mapping_it
            },
            output_filename,
        )
        if emit_name == "json_schema"
        else gen_file(
            name_tpl,
            input_mapping_it,
            parse_name,
            emit_name,
            output_filename,
            prepend,
            emit_call,
            emit_and_infer_imports,
            emit_default_doc,
            decorator_list,
            no_word_wrap,
            imports,
        )
    )

cdd.compound.gen_utils

cdd.compound.gen_utils

Utility functions for cdd.gen

Parameters:

Name Type Description Default

file_to_input_mapping

file_to_input_mapping(filepath, parse_name)

Create an input_mapping from a given file, i.e. Dict[str, AST]

Parameters:

Name Type Description Default
filepath str

Location of JSON or Python file

required
parse_name Literal["argparse", "class", "function", "json_schema", "pydantic", "sqlalchemy", "sqlalchemy_table", "sqlalchemy_hybrid", "infer"]

Which type to parse.

required

Returns:

Name Type Description
return_type dict``

Dictionary of string (name) to AST node

Source code in cdd/compound/gen_utils.py
def file_to_input_mapping(filepath, parse_name):
    """
    Create an `input_mapping` from a given file, i.e. Dict[str, AST]

    :param filepath: Location of JSON or Python file
    :type filepath: ```str```

    :param parse_name: Which type to parse.
    :type parse_name: ```Literal["argparse", "class", "function", "json_schema",
                                 "pydantic", "sqlalchemy", "sqlalchemy_table", "sqlalchemy_hybrid", "infer"]```

    :return: Dictionary of string (name) to AST node
    :rtype: ```dict``
    """
    if (
        parse_name == "json_schema"
        or parse_name == "infer"
        and filepath.endswith("{}json".format(path.extsep))
    ):
        with open(filepath, "rt") as f:
            json_contents = load(f)
        name: str = path.basename(filepath)
        if "name" not in json_contents:
            json_contents["name"] = pascal_to_upper_camelcase(name)
        input_mapping = {name: json_contents}  # type: dict[str, Union[str, AST]]
    else:
        with open(filepath, "rt") as f:
            mod = ast_parse(f.read())

        input_mapping = dict(
            map(
                lambda node: (node.name, node),
                filter(
                    rpartial(
                        isinstance,
                        (
                            tuple(kind2instance_type.values())
                            if parse_name == "infer"
                            else kind2instance_type[parse_name]
                        ),
                    ),
                    mod.body,
                ),
            ),
        )
    return input_mapping

gen_file

gen_file(name_tpl, input_mapping_it, parse_name, emit_name, output_filename, prepend, emit_call, emit_and_infer_imports, emit_default_doc, decorator_list, no_word_wrap, imports, functions_and_classes=None)

Generate Python file of containing input_mapping_it.values converted to emit_name

Parameters:

Name Type Description Default
name_tpl str

Template for the name, e.g., {name}Config.

required
input_mapping_it Iterator[tuple[str, AST]]

Import location of mapping/2-tuple collection.

required
parse_name Literal["argparse", "class", "function", "json_schema", "pydantic", "sqlalchemy", "sqlalchemy_table", "sqlalchemy_hybrid"]

Which type to parse.

required
emit_name Literal["argparse", "class", "function", "json_schema", "pydantic", "sqlalchemy", "sqlalchemy_table", "sqlalchemy_hybrid"]

Which type to generate.

required
output_filename str

Output file to write to

required
prepend Optional[str]

Prepend file with this. Use ' ' for newlines.

required
emit_call bool

Whether to emit a __call__ method from the _internal IR subdict

required
emit_and_infer_imports bool

Whether to emit and infer imports at the top of the generated code

required
emit_default_doc bool

Whether help/docstring should include 'With default' text

required
decorator_list Optional[Union[List[str], List[]]]

List of decorators

required
no_word_wrap Optional[Literal[True]]

Whether word-wrap is disabled (on emission).

required
imports str

Import to preclude in Python file

required
functions_and_classes Optional[tuple[AST]]

Functions and classes that have been preparsed

required
Source code in cdd/compound/gen_utils.py
def gen_file(
    name_tpl,
    input_mapping_it,
    parse_name,
    emit_name,
    output_filename,
    prepend,
    emit_call,
    emit_and_infer_imports,
    emit_default_doc,
    decorator_list,
    no_word_wrap,
    imports,
    functions_and_classes=None,
):
    """
    Generate Python file of containing `input_mapping_it`.values converted to `emit_name`

    :param name_tpl: Template for the name, e.g., `{name}Config`.
    :type name_tpl: ```str```

    :param input_mapping_it: Import location of mapping/2-tuple collection.
    :type input_mapping_it: ```Iterator[tuple[str, AST]]```

    :param parse_name: Which type to parse.
    :type parse_name: ```Literal["argparse", "class", "function", "json_schema",
                                 "pydantic", "sqlalchemy", "sqlalchemy_table", "sqlalchemy_hybrid"]```

    :param emit_name: Which type to generate.
    :type emit_name: ```Literal["argparse", "class", "function", "json_schema",
                                "pydantic", "sqlalchemy", "sqlalchemy_table", "sqlalchemy_hybrid"]```

    :param output_filename: Output file to write to
    :type output_filename: ```str```

    :param prepend: Prepend file with this. Use '\n' for newlines.
    :type prepend: ```Optional[str]```

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

    :param emit_and_infer_imports: Whether to emit and infer imports at the top of the generated code
    :type emit_and_infer_imports: ```bool```

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

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

    :param no_word_wrap: Whether word-wrap is disabled (on emission).
    :type no_word_wrap: ```Optional[Literal[True]]```

    :param imports: Import to preclude in Python file
    :type imports: ```str```

    :param functions_and_classes: Functions and classes that have been preparsed
    :type functions_and_classes: ```Optional[tuple[AST]]```
    """
    parsed_ast = gen_module(
        decorator_list,
        emit_and_infer_imports,
        emit_call,
        emit_default_doc,
        emit_name,
        functions_and_classes,
        imports,
        input_mapping_it,
        name_tpl,
        no_word_wrap,
        parse_name,
        prepend,
    )
    assert (
        len(parsed_ast.body) > 1
        or not isinstance(parsed_ast.body[0], Assign)
        and any(
            filter(
                lambda target: isinstance(target, Name) and target.id == "__all__",
                parsed_ast.body[0].targets,
            )
        )
    ), "Nothing will be append to {!r}".format(output_filename)
    with open(output_filename, "a") as f:
        f.write(to_code(parsed_ast))

gen_module

gen_module(decorator_list, emit_and_infer_imports, emit_call, emit_default_doc, emit_name, functions_and_classes, imports, input_mapping_it, name_tpl, no_word_wrap, parse_name, prepend, global__all__=None)

Generate Python module input_mapping_it.values converted to emit_name

Parameters:

Name Type Description Default
name_tpl str

Template for the name, e.g., {name}Config.

required
input_mapping_it Iterator[tuple[str, AST]]

Import location of mapping/2-tuple collection.

required
parse_name Literal["argparse", "class", "function", "json_schema", "pydantic", "sqlalchemy", "sqlalchemy_table", "sqlalchemy_hybrid"]

Which type to parse.

required
emit_name Literal["argparse", "class", "function", "json_schema", "pydantic", "sqlalchemy", "sqlalchemy_table", "sqlalchemy_hybrid"]

Which type to generate.

required
prepend Optional[str]

Prepend file with this. Use ' ' for newlines.

required
emit_call bool

Whether to emit a __call__ method from the _internal IR subdict

required
emit_and_infer_imports bool

Whether to emit and infer imports at the top of the generated code

required
emit_default_doc bool

Whether help/docstring should include 'With default' text

required
decorator_list Optional[Union[List[str], List[]]]

List of decorators

required
no_word_wrap Optional[Literal[True]]

Whether word-wrap is disabled (on emission).

required
imports Optional[str]

Import to preclude in Python file

required
functions_and_classes Optional[Tuple[AST]]

Functions and classes that have been preparsed

required
global__all__ list[str]

__all__ symbols for that magic

required

Returns:

Name Type Description
return_type Module

Module with everything contained inside, e.g., all the imports, parsed out functions and classes

Source code in cdd/compound/gen_utils.py
def gen_module(
    decorator_list,
    emit_and_infer_imports,
    emit_call,
    emit_default_doc,
    emit_name,
    functions_and_classes,
    imports,
    input_mapping_it,
    name_tpl,
    no_word_wrap,
    parse_name,
    prepend,
    global__all__=None,
):
    """
    Generate Python module `input_mapping_it`.values converted to `emit_name`

    :param name_tpl: Template for the name, e.g., `{name}Config`.
    :type name_tpl: ```str```

    :param input_mapping_it: Import location of mapping/2-tuple collection.
    :type input_mapping_it: ```Iterator[tuple[str, AST]]```

    :param parse_name: Which type to parse.
    :type parse_name: ```Literal["argparse", "class", "function", "json_schema",
                                 "pydantic", "sqlalchemy", "sqlalchemy_table", "sqlalchemy_hybrid"]```

    :param emit_name: Which type to generate.
    :type emit_name: ```Literal["argparse", "class", "function", "json_schema",
                                "pydantic", "sqlalchemy", "sqlalchemy_table", "sqlalchemy_hybrid"]```

    :param prepend: Prepend file with this. Use '\n' for newlines.
    :type prepend: ```Optional[str]```

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

    :param emit_and_infer_imports: Whether to emit and infer imports at the top of the generated code
    :type emit_and_infer_imports: ```bool```

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

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

    :param no_word_wrap: Whether word-wrap is disabled (on emission).
    :type no_word_wrap: ```Optional[Literal[True]]```

    :param imports: Import to preclude in Python file
    :type imports: ```Optional[str]```

    :param functions_and_classes: Functions and classes that have been preparsed
    :type functions_and_classes: ```Optional[Tuple[AST]]```

    :param global__all__: `__all__` symbols for that magic
    :type global__all__: ```list[str]```

    :return: Module with everything contained inside, e.g., all the imports, parsed out functions and classes
    :rtype: ```Module```
    """
    if global__all__ is None:
        global__all__ = []  # type: list[str]
    if functions_and_classes is None:
        functions_and_classes = get_functions_and_classes(
            decorator_list,
            emit_call,
            emit_default_doc,
            emit_name,
            global__all__,
            input_mapping_it,
            name_tpl,
            no_word_wrap,
            parse_name,
        )  # type: tuple[Union[FunctionDef, ClassDef]]
    if emit_and_infer_imports:
        imports: str = "{}{}".format(
            imports or "",
            " ".join(
                map(
                    to_code,
                    optimise_imports(chain(*map(infer_imports, functions_and_classes))),
                )
            ),
        )

    # Too many params! - Clean things up for debugging:
    del (
        decorator_list,
        emit_call,
        emit_default_doc,
        emit_name,
        input_mapping_it,
        name_tpl,
        no_word_wrap,
        parse_name,
    )

    content: str = "{prepend}{imports}\n{functions_and_classes}\n{__all__}".format(
        prepend="" if prepend is None else prepend,
        imports=imports,  # TODO: Optimize imports programmatically (akin to `autoflake --remove-all-unused-imports`)
        functions_and_classes="\n\n".join(map(to_code, functions_and_classes)),
        __all__=to_code(
            Assign(
                targets=[Name("__all__", Store(), lineno=None, col_offset=None)],
                value=ast.parse(  # `TypeError: Type List cannot be instantiated; use list() instead`
                    str(
                        list(
                            map(
                                lambda s: s.rstrip("\n").strip("'").strip('"'),
                                map(to_code, map(set_value, global__all__)),
                            )
                        )
                    )
                )
                .body[0]
                .value,
                expr=None,
                lineno=None,
                **maybe_type_comment,
            )
        ),
    )
    parsed_ast: Module = ast.parse(content)
    # TODO: Shebang line first, then docstring, then imports
    doc_str: Optional[str] = ast.get_docstring(parsed_ast, clean=True)
    whole = tuple(
        map(
            lambda node: (
                (node, None) if isinstance(node, (Import, ImportFrom)) else (None, node)
            ),
            parsed_ast.body,
        )
    )
    parsed_ast.body = list(
        filter(
            None,
            chain.from_iterable(
                (
                    parsed_ast.body[:1] if doc_str else iter(()),
                    sorted(
                        map(itemgetter(0), whole),
                        key=lambda import_from: getattr(import_from, "module", None)
                        == "__future__",
                        reverse=True,
                    ),
                    map(itemgetter(1), whole[1:] if doc_str else whole),
                ),
            ),
        )
    )
    return parsed_ast

get_emit_kwarg

get_emit_kwarg(decorator_list, emit_call, emit_name, name_tpl, name)

Emit keyword arguments have different requirements dependent on emitter Determine correct one, and always include the name.

Parameters:

Name Type Description Default
decorator_list Optional[Union[List[str], List[]]]

List of decorators

required
emit_name Literal["argparse", "class", "function", "json_schema", "pydantic", "sqlalchemy", "sqlalchemy_table", "sqlalchemy_hybrid"]

Which type to generate.

required
emit_call bool

Whether to emit a __call__ method from the _internal IR subdict

required
name_tpl str

Template for the name, e.g., {name}Config.

required
name str

Interpolates into name_tpl

required

Returns:

Name Type Description
return_type dict``

Dictionary of keyword arguments targeted the specialised emit function.

Source code in cdd/compound/gen_utils.py
def get_emit_kwarg(decorator_list, emit_call, emit_name, name_tpl, name):
    """
    Emit keyword arguments have different requirements dependent on emitter
    Determine correct one, and always include the name.

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

    :param emit_name: Which type to generate.
    :type emit_name: ```Literal["argparse", "class", "function", "json_schema",
                                "pydantic", "sqlalchemy", "sqlalchemy_table", "sqlalchemy_hybrid"]```

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

    :param name_tpl: Template for the name, e.g., `{name}Config`.
    :type name_tpl: ```str```

    :param name: Interpolates into `name_tpl`
    :type name: ```str```

    :return: Dictionary of keyword arguments targeted the specialised emit function.
    :rtype: ```dict``
    """
    return (
        lambda _name: {
            "argparse_function": {"function_name": _name},
            "class_": {
                "class_name": _name,
                "decorator_list": decorator_list,
                "emit_call": emit_call,
            },
            "function": {
                "function_name": _name,
            },
            "json_schema": {
                "identifier": _name,
            },
            "sqlalchemy": {"table_name": _name},
            "sqlalchemy_hybrid": {"table_name": _name},
            "sqlalchemy_table": {"table_name": _name},
        }[emit_name]
    )(None if name == "infer" else ensure_valid_identifier(name_tpl.format(name=name)))

get_functions_and_classes

get_functions_and_classes(decorator_list, emit_call, emit_default_doc, emit_name, global__all__, input_mapping_it, name_tpl, no_word_wrap, parse_name)

Emitted functions and/or classes

Parameters:

Name Type Description Default
decorator_list Optional[Union[List[str], List[]]]

List of decorators

required
emit_call bool

Whether to emit a __call__ method from the _internal IR subdict

required
emit_default_doc bool

Whether help/docstring should include 'With default' text

required
emit_name Literal["argparse", "class", "function", "json_schema", "pydantic", "sqlalchemy", "sqlalchemy_table", "sqlalchemy_hybrid"]

Which type to generate.

required
global__all__ list[str]

__all__ symbols for that magic

required
input_mapping_it Iterator[Tuple[str,AST]]

Import location of mapping/2-tuple collection.

required
name_tpl str

Template for the name, e.g., {name}Config.

required
no_word_wrap Optional[Literal[True]]

Whether word-wrap is disabled (on emission).

required
parse_name Literal["argparse", "class", "function", "json_schema", "pydantic", "sqlalchemy", "sqlalchemy_table", "sqlalchemy_hybrid"]

Which type to parse.

required

Returns:

Name Type Description
return_type tuple[Union[FunctionDef, ClassDef]]

Side-effect of appending __all__, this returns emitted values out of input_mapping_it

Source code in cdd/compound/gen_utils.py
def get_functions_and_classes(
    decorator_list,
    emit_call,
    emit_default_doc,
    emit_name,
    global__all__,
    input_mapping_it,
    name_tpl,
    no_word_wrap,
    parse_name,
):
    """
    Emitted functions and/or classes

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

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

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

    :param emit_name: Which type to generate.
    :type emit_name: ```Literal["argparse", "class", "function", "json_schema",
                                "pydantic", "sqlalchemy", "sqlalchemy_table", "sqlalchemy_hybrid"]```


    :param global__all__: `__all__` symbols for that magic
    :type global__all__: ```list[str]```

    :param input_mapping_it: Import location of mapping/2-tuple collection.
    :type input_mapping_it: ```Iterator[Tuple[str,AST]]```

    :param name_tpl: Template for the name, e.g., `{name}Config`.
    :type name_tpl: ```str```

    :param no_word_wrap: Whether word-wrap is disabled (on emission).
    :type no_word_wrap: ```Optional[Literal[True]]```

    :param parse_name: Which type to parse.
    :type parse_name: ```Literal["argparse", "class", "function", "json_schema",
                                 "pydantic", "sqlalchemy", "sqlalchemy_table", "sqlalchemy_hybrid"]```

    :return: Side-effect of appending `__all__`, this returns emitted values out of `input_mapping_it`
    :rtype: ```tuple[Union[FunctionDef, ClassDef]]```
    """
    emitter = get_emitter(emit_name)
    return tuple(
        print("\nGenerating: {name!r}".format(name=name))
        or global__all__.append(name_tpl.format(name=name))
        or emitter(
            get_parser(obj, parse_name)(obj),
            emit_default_doc=emit_default_doc,
            word_wrap=no_word_wrap is None,
            **get_emit_kwarg(decorator_list, emit_call, emit_name, name_tpl, name),
        )
        for name, obj in input_mapping_it
    )

get_input_mapping_from_path

get_input_mapping_from_path(emit_name, module_path, symbol_name)

Given (module_path, symbol_name) acquire file path, ast.parse out all top-level symbols matching emit_name

Parameters:

Name Type Description Default
emit_name Literal["argparse", "class", "function", "json_schema", "pydantic", "sqlalchemy", "sqlalchemy_table", "sqlalchemy_hybrid"]

Which type to generate.

required
module_path str

Module path

required
symbol_name str

Symbol to import from module

required

Returns:

Name Type Description
return_type dict

Dictionary of (name, AST) where AST is produced by a cdd emitter matching emit_name

Source code in cdd/compound/gen_utils.py
def get_input_mapping_from_path(emit_name, module_path, symbol_name):
    """
    Given (module_path, symbol_name) acquire file path, `ast.parse` out all top-level symbols matching `emit_name`

    :param emit_name: Which type to generate.
    :type emit_name: ```Literal["argparse", "class", "function", "json_schema",
                                "pydantic", "sqlalchemy", "sqlalchemy_table", "sqlalchemy_hybrid"]```

    :param module_path: Module path
    :type module_path: ```str```

    :param symbol_name: Symbol to import from module
    :type symbol_name: ```str```

    :return: Dictionary of (name, AST) where AST is produced by a cdd emitter matching `emit_name`
    :rtype: ```dict```
    """
    module_filepath = find_module_filepath(module_path, symbol_name)
    with open(module_filepath, "rt") as f:
        input_ast_mod: Module = ast.parse(f.read())
    type_instance_must_be = kind2instance_type.get(emit_name, (FunctionDef, ClassDef))
    input_mapping = dict(
        map(
            lambda ir: (ir["name"], ir),
            map(
                lambda node: get_parser(node, emit_name)(node),
                filter(
                    rpartial(
                        isinstance,
                        type_instance_must_be,
                    ),
                    input_ast_mod.body,
                ),
            ),
        )
    )
    assert input_mapping, "Nothing parsed out of {!r}".format(module_filepath)
    return input_mapping

get_parser

get_parser(node, parse_name)

Get parser function specialised for input node

Parameters:

Name Type Description Default
node AST

Node to parse

required
parse_name Literal["argparse", "class", "function", "json_schema", "pydantic", "sqlalchemy", "sqlalchemy_table", "sqlalchemy_hybrid","infer"]

Which type to parse.

required

Returns:

Name Type Description
return_type Callable[[...], dict]`

Function which returns intermediate_repr

Source code in cdd/shared/parse/utils/parser_utils.py
def get_parser(node, parse_name):
    """
    Get parser function specialised for input `node`

    :param node: Node to parse
    :type node: ```AST```

    :param parse_name: Which type to parse.
    :type parse_name: ```Literal["argparse", "class", "function", "json_schema",
                                 "pydantic", "sqlalchemy", "sqlalchemy_table", "sqlalchemy_hybrid","infer"]```

    :return: Function which returns intermediate_repr
    :rtype: ```Callable[[...], dict]````
    """
    if parse_name in (None, "infer"):
        parse_name: str = infer(node)
    parse_name: str = {
        "class": "class_",
        "sqlalchemy_hybrid": "sqlalchemy",
        "sqlalchemy_table": "sqlalchemy",
    }.get(parse_name, parse_name)
    return getattr(import_module(".".join(("cdd", parse_name, "parse"))), parse_name)

cdd.compound.openapi

cdd.compound.openapi

Parsers and emitters for OpenAPI

Parameters:

Name Type Description Default

cdd.compound.openapi.emit

cdd.compound.openapi.emit

OpenAPI emitter function(s)

Parameters:

Name Type Description Default

openapi

openapi(name_model_route_id_cruds)

Emit OpenAPI dict

Parameters:

Name Type Description Default
name_model_route_id_cruds Iterable[NameModelRouteIdCrud]

Collection of (name, model, route, id, crud)

required

Returns:

Name Type Description
return_type dict

OpenAPI dict

Source code in cdd/compound/openapi/emit.py
def openapi(name_model_route_id_cruds):
    """
    Emit OpenAPI dict

    :param name_model_route_id_cruds: Collection of (name, model, route, id, crud)
    :type name_model_route_id_cruds: ```Iterable[NameModelRouteIdCrud]```

    :return: OpenAPI dict
    :rtype: ```dict```
    """
    paths, components = {}, {
        "requestBodies": {},
        "schemas": {
            "ServerError": {
                k: v for k, v in server_error_schema.items() if not k.startswith("$")
            }
        },
    }

    deque(
        map(
            lambda name_model_route_id_crud: components_paths_from_name_model_route_id_crud(
                components, paths, *name_model_route_id_crud
            ),
            name_model_route_id_cruds,
        ),
        maxlen=0,
    )

    return {
        "openapi": "3.0.0",
        "info": {"version": "0.0.1", "title": "REST API"},
        # "servers": [{"url": "https://example.io/v1"}],
        "components": components,
        "paths": paths,
    }

cdd.compound.openapi.gen_openapi

cdd.compound.openapi.gen_openapi

All encompassing solution to generating the OpenAPI schema

Parameters:

Name Type Description Default

openapi_bulk

openapi_bulk(app_name, model_paths, routes_paths)

Generate OpenAPI from models, routes on app

Parameters:

Name Type Description Default
app_name str

Variable name (Bottle App)

required
model_paths list[str]

The path/module-resolution(s) whence the model(s) can be found

required
routes_paths list[str]

The path/module-resolution(s) whence the route(s) can be found

required

Returns:

Name Type Description
return_type dict

OpenAPI dictionary

Source code in cdd/compound/openapi/gen_openapi.py
def openapi_bulk(app_name, model_paths, routes_paths):
    """
    Generate OpenAPI from models, routes on app

    :param app_name: Variable name (Bottle App)
    :type app_name: ```str```

    :param model_paths: The path/module-resolution(s) whence the model(s) can be found
    :type model_paths: ```list[str]```

    :param routes_paths: The path/module-resolution(s) whence the route(s) can be found
    :type routes_paths: ```list[str]```

    :return: OpenAPI dictionary
    :rtype: ```dict```
    """
    request_bodies: OpenAPI_requestBodies = {}

    def parse_model(filename):
        """
        :param filename: The filename to open and parse AST out of
        :type filename: ```str```

        :return: Iterable of tuples of the found kind
        :rtype: ```Iterable[tuple[AST, ...], ...]```
        """
        with open(filename, "rb") as f:
            parsed_ast: Module = ast.parse(f.read())

        return filter(
            lambda node: (infer(node) or "").startswith("sqlalchemy"),
            filter(rpartial(isinstance, (Call, ClassDef)), ast.walk(parsed_ast)),
        )

    def parse_route(filename):
        """
        :param filename: The filename to open and parse AST out of
        :type filename: ```str```

        :return: Iterable of tuples of the found kind
        :rtype: ```Iterable[tuple[AST, ...], ...]```
        """
        with open(filename, "rb") as f:
            parsed_ast: Module = ast.parse(f.read())

        return filter(
            lambda node: next(
                get_route_meta(Module(body=[node], type_ignores=[], stmt=None))
            )[1]
            == app_name,
            filter(rpartial(isinstance, FunctionDef), parsed_ast.body),
        )

    def construct_parameters_and_request_bodies(route, path_dict):
        """
        Construct `parameters` and `requestBodies`

        :param route: Route path, like "/api/foo"
        :type route: ```str```

        :param path_dict: OpenAPI paths key
        :type path_dict: ```dict```

        :return: (route, path_dict) with `"parameters"` key potentially set
        :rtype: ```tuple[str, dict]```
        """
        if ":" in route:
            path_dict["parameters"] = []
            object_name: str = path_dict.get(
                "get", path_dict.get("delete", {"summary": "`Object`"})
            )["summary"]
            fst: int = object_name.find("`")
            object_name: str = (
                object_name[fst + 1 : object_name.find("`", fst + 1)] or "Object"
            )

            route: str = "/".join(
                map(
                    lambda r: (
                        (
                            lambda pk: (
                                path_dict["parameters"].append(
                                    {
                                        "description": (
                                            "Primary key of target `{}`".format(
                                                object_name
                                            )
                                        ),
                                        "in": "path",
                                        "name": pk,
                                        "required": True,
                                        "schema": {"type": "string"},
                                    }
                                )
                                or "{{{}}}".format(pk)
                            )
                        )(r[1:])
                        if r.startswith(":")
                        else r
                    ),
                    route.split("/"),
                )
            )

        request_bodies.update(
            map(
                lambda body_name: (
                    body_name,
                    (
                        lambda key: {
                            "content": {
                                "application/json": {
                                    "schema": {
                                        "$ref": "#/components/schemas/{key}".format(
                                            key=key
                                        )
                                    }
                                }
                            },
                            "description": "A `{key}` object.".format(key=key),
                            "required": True,
                        }
                    )(body_name.rpartition("Body")[0]),
                ),
                map(
                    lambda ref: ref.rpartition("/")[2],
                    map(
                        itemgetter("$ref"),
                        filter(
                            None,
                            map(
                                rpartial(dict.get, "requestBody"),
                                filter(rpartial(isinstance, dict), path_dict.values()),
                            ),
                        ),
                    ),
                ),
            )
        )

        return route, path_dict

    return {
        "openapi": "3.0.0",
        "info": {"version": "0.0.1", "title": "REST API"},
        # "servers": [{"url": "https://example.io/v1"}],
        "components": {
            "requestBodies": request_bodies,
            "schemas": {
                key: {k: v for k, v in val.items() if not k.startswith("$")}
                for key, val in dict(
                    map(
                        lambda table: (
                            table["name"].replace("_tbl", "", 1).title(),
                            cdd.json_schema.emit.json_schema(table),
                        ),
                        map(
                            lambda node: (
                                cdd.sqlalchemy.parse.sqlalchemy_table(node)
                                if isinstance(node, (AnnAssign, Assign, Call))
                                else cdd.sqlalchemy.parse.sqlalchemy(node)
                            ),
                            chain.from_iterable(map(parse_model, model_paths)),
                        ),
                    ),
                    ServerError=server_error_schema,
                ).items()
            },
        },
        "paths": dict(
            map(
                lambda k_v: construct_parameters_and_request_bodies(
                    k_v[0], update_d(*map(itemgetter(1), k_v[1]))
                ),
                groupby(
                    map(
                        lambda route: (
                            get_value(route.decorator_list[0].args[0]),
                            {
                                route.decorator_list[
                                    0
                                ].func.attr: cdd.routes.parse.bottle.bottle(route)
                            },
                        ),
                        chain.from_iterable(map(parse_route, routes_paths)),
                    ),
                    key=itemgetter(0),
                ),
            )
        ),
    }

cdd.compound.openapi.gen_routes

cdd.compound.openapi.gen_routes

Generate routes

Parameters:

Name Type Description Default

gen_routes

gen_routes(app, model_path, model_name, crud, route)

Generate route(s)

Parameters:

Name Type Description Default
app str

Variable name (Bottle App)

required
model_path str

The path/module-resolution whence the model is

required
model_name str

Name of the model to recover from the model_path

required
crud Union[Literal['C', 'R'], Literal['C', 'U'], Literal['C', 'D'], Literal['R', 'C'], Literal['R', 'U'], Literal['R', 'D'], Literal['U', 'C'], Literal['U', 'R'], Literal['U', 'D'], Literal['D', 'C'], Literal['D', 'R'], Literal['D', 'U'], Literal['C', 'R', 'U'], Literal['C', 'R', 'D'], Literal['C', 'U', 'R'], Literal['C', 'U', 'D'], Literal['C', 'D', 'R'], Literal['C', 'D', 'U'], Literal['R', 'C', 'U'], Literal['R', 'C', 'D'], Literal['R', 'U', 'C'], Literal['R', 'U', 'D'], Literal['R', 'D', 'C'], Literal['R', 'D', 'U'], Literal['U', 'C', 'R'], Literal['U', 'C', 'D'], Literal['U', 'R', 'C'], Literal['U', 'R', 'D'], Literal['U', 'D', 'C'], Literal['U', 'D', 'R'], Literal['D', 'C', 'R'], Literal['D', 'C', 'U'], Literal['D', 'R', 'C'], Literal['D', 'R', 'U'], Literal['D', 'U', 'C'], Literal['D', 'U', 'R']]

(C)reate (R)ead (U)pdate (D)elete, like "CRUD" for all or "CD" for "Create" and "Delete"

required
route str

The path of the resource

required

Returns:

Name Type Description
return_type Iterator[FunctionDef]

Iterator of functions representing relevant CRUD operations

Source code in cdd/compound/openapi/gen_routes.py
def gen_routes(app, model_path, model_name, crud, route):
    """
    Generate route(s)

    :param app: Variable name (Bottle App)
    :type app: ```str```

    :param model_path: The path/module-resolution whence the model is
    :type model_path: ```str```

    :param model_name: Name of the model to recover from the `model_path`
    :type model_name: ```str```

    :param crud: (C)reate (R)ead (U)pdate (D)elete, like "CRUD" for all or "CD" for "Create" and "Delete"
    :type crud: ```Union[Literal['C', 'R'], Literal['C', 'U'], Literal['C', 'D'], Literal['R', 'C'],
                         Literal['R', 'U'], Literal['R', 'D'], Literal['U', 'C'], Literal['U', 'R'],
                         Literal['U', 'D'], Literal['D', 'C'], Literal['D', 'R'], Literal['D', 'U'],
                         Literal['C', 'R', 'U'], Literal['C', 'R', 'D'], Literal['C', 'U', 'R'],
                         Literal['C', 'U', 'D'], Literal['C', 'D', 'R'], Literal['C', 'D', 'U'],
                         Literal['R', 'C', 'U'], Literal['R', 'C', 'D'], Literal['R', 'U', 'C'],
                         Literal['R', 'U', 'D'], Literal['R', 'D', 'C'], Literal['R', 'D', 'U'],
                         Literal['U', 'C', 'R'], Literal['U', 'C', 'D'], Literal['U', 'R', 'C'],
                         Literal['U', 'R', 'D'], Literal['U', 'D', 'C'], Literal['U', 'D', 'R'],
                         Literal['D', 'C', 'R'], Literal['D', 'C', 'U'], Literal['D', 'R', 'C'],
                         Literal['D', 'R', 'U'], Literal['D', 'U', 'C'], Literal['D', 'U', 'R']]```

    :param route: The path of the resource
    :type route: ```str```

    :return: Iterator of functions representing relevant CRUD operations
    :rtype: ```Iterator[FunctionDef]```
    """
    model_path: str = filename_from_mod_or_filename(model_path)

    assert path.isfile(model_path)
    with open(model_path, "rt") as f:
        mod: Module = ast.parse(f.read())

    sqlalchemy_node: Optional[ClassDef] = next(
        filter(
            lambda node: isinstance(node, ClassDef)
            and (
                node.name == model_name
                or isinstance(node.name, Name)
                and node.name.id == model_name
            ),
            ast.walk(mod),
        ),
        None,
    )
    sqlalchemy_ir: IntermediateRepr = cdd.sqlalchemy.parse.sqlalchemy(
        Module(body=[sqlalchemy_node], stmt=None, type_ignores=[])
    )
    primary_key: str = next(
        map(
            itemgetter(0),
            filter(
                lambda param: param[1]["doc"].startswith("[PK]"),
                sqlalchemy_ir["params"].items(),
            ),
        ),
        next(iter(sqlalchemy_ir["params"].keys())),
    )
    _route_config: dict[str, Union[str, int]] = {
        "app": app,
        "name": model_name,
        "route": route,
        "variant": -1,
    }
    routes: List[str] = []
    if "C" in crud:
        routes.append(cdd.routes.emit.bottle.create(**_route_config))
    _route_config["primary_key"] = primary_key

    funcs: dict[str, Optional[Callable[[str, str, str, Any, int], str]]] = {
        "R": cdd.routes.emit.bottle.read,
        "U": None,
        "D": cdd.routes.emit.bottle.destroy,
    }
    routes.extend(funcs[key](**_route_config) for key in funcs if key in crud)
    return (
        map(itemgetter(0), map(attrgetter("body"), map(ast.parse, routes))),
        primary_key,
    )

upsert_routes

upsert_routes(app, routes, routes_path, route, primary_key)

Upsert the routes to the routes_path, on merge use existing body and replace interface/prototype

Parameters:

Name Type Description Default
app str

Variable name (Bottle App)

required
routes Iterator[FunctionDef]

Iterator of functions representing relevant CRUD operations

required
route str

The path of the resource

required
primary_key str

The primary key or id to lookup on for the route

required
routes_path str

The path/module-resolution whence the routes are / will be

required
Source code in cdd/compound/openapi/gen_routes.py
def upsert_routes(app, routes, routes_path, route, primary_key):
    """
    Upsert the `routes` to the `routes_path`, on merge use existing body and replace interface/prototype

    :param app: Variable name (Bottle App)
    :type app: ```str```

    :param routes: Iterator of functions representing relevant CRUD operations
    :type routes: ```Iterator[FunctionDef]```

    :param route: The path of the resource
    :type route: ```str```

    :param primary_key: The primary key or id to lookup on for the route
    :type primary_key: ```str```

    :param routes_path: The path/module-resolution whence the routes are / will be
    :type routes_path: ```str```
    """
    routes_path: str = filename_from_mod_or_filename(routes_path)

    if not path.isfile(routes_path):
        with open(routes_path, "wt") as f:
            f.write(
                "\n\n".join(
                    chain.from_iterable(
                        (
                            (
                                route_prelude.replace(
                                    "rest_api =", "{app} =".format(app=app)
                                ),
                            ),
                            map(to_code, routes),
                        )
                    )
                )
            )
        return

    with open(routes_path, "rt") as f:
        mod: Module = ast.parse(f.read())

    def get_names(functions):
        """
        Derive a method_name -> FunctionDef dictionary

        :param functions: Routing functions
        :type functions: ```Iterator[FunctionDef]```

        :return: Dict of `method_name` to `FunctionDef`
        :rtype: ```Dict[str, FunctionDef]```
        """
        return dict(
            map(
                lambda func: (
                    next(
                        map(
                            lambda call: call.func.attr,
                            filter(
                                lambda call: all(
                                    (
                                        isinstance(call.func, Attribute),
                                        call.func.attr in methods,
                                    )
                                ),
                                filter(rpartial(isinstance, Call), func.decorator_list),
                            ),
                        )
                    ),
                    func,
                ),
                functions,
            )
        )

    routes_required: Dict[str, FunctionDef] = get_names(routes)
    routes_existing: Dict[str, FunctionDef] = get_names(
        filter(
            lambda node: any(
                filter(
                    lambda call: all(
                        (
                            isinstance(call.func, Attribute),
                            call.func.attr in methods,
                            get_value(call.args[0])
                            == "{route}{rest}".format(
                                route=route,
                                rest=(
                                    ""
                                    if call.func.attr == "post"
                                    else "/:{primary_key}".format(
                                        primary_key=primary_key
                                    )
                                ),
                            ),
                            call.func.value.id == app,
                        )
                    ),
                    filter(rpartial(isinstance, Call), node.decorator_list),
                )
            ),
            filter(rpartial(isinstance, FunctionDef), ast.walk(mod)),
        )
    )
    missing_routes = (
        routes_required.keys() & routes_existing.keys() ^ routes_required.keys()
    )  # type: dict_keys[str, str]

    if not missing_routes:
        return

    with open(routes_path, "a") as f:
        f.write(
            "\n\n".join(
                map(
                    to_code,
                    map(
                        routes_required.__getitem__,
                        sorted(
                            missing_routes,
                            key={
                                "post": 0,
                                "get": 1,
                                "update": 2,
                                "delete": 3,
                            }.__getitem__,
                        ),
                    ),
                )
            )
        )

cdd.compound.openapi.parse

cdd.compound.openapi.parse

OpenAPI parsers

Parameters:

Name Type Description Default

openapi

openapi(openapi_str, routes_dict, summary)

OpenAPI parser

Parameters:

Name Type Description Default
openapi_str str

The OpenAPI str

required
routes_dict dict

Has keys ("route", "name", "method")

required
summary str

summary string (used as fallback)

required

Returns:

Name Type Description
return_type

OpenAPI dictionary

Source code in cdd/compound/openapi/parse.py
def openapi(openapi_str, routes_dict, summary):
    """
    OpenAPI parser

    :param openapi_str: The OpenAPI str
    :type openapi_str: ```str```

    :param routes_dict: Has keys ("route", "name", "method")
    :type routes_dict: ```dict```

    :param summary: summary string (used as fallback)
    :type summary: ```str```

    :return: OpenAPI dictionary
    """
    entities: List[str] = extract_entities(openapi_str)

    non_error_entity: Optional[str] = None

    for entity in entities:
        openapi_str: str = openapi_str.replace(
            "$ref: ```{entity}```".format(entity=entity),
            "{{'$ref': '#/components/schemas/{entity}'}}".format(entity=entity),
        )
        if entity != "ServerError":
            non_error_entity: str = entity
    openapi_d: dict = (loads if openapi_str.startswith("{") else safe_load)(openapi_str)
    if non_error_entity is not None:
        openapi_d["summary"] = "{located} `{entity}` object.".format(
            located="A", entity=non_error_entity
        )
        if routes_dict["method"] in frozenset(("post", "patch")):
            openapi_d["requestBody"] = {
                "$ref": "#/components/requestBodies/{entity}Body".format(
                    entity=non_error_entity
                ),
                "required": True,
            }
    else:
        openapi_d["summary"] = summary
    if "responses" in openapi_d:
        openapi_d["responses"] = {k: v or {} for k, v in openapi_d["responses"].items()}
    return openapi_d

cdd.compound.openapi.utils

cdd.compound.openapi.utils

OpenAPI parser and emitter utility module

Parameters:

Name Type Description Default

cdd.compound.openapi.utils.emit_openapi_utils

cdd.compound.openapi.utils.emit_openapi_utils

Utility functions for cdd.emit.openapi

Parameters:

Name Type Description Default

components_paths_from_name_model_route_id_crud

components_paths_from_name_model_route_id_crud(components, paths, name, model, route, _id, crud)

Update components and paths from (name, model, route, _id, crud)

Parameters:

Name Type Description Default
components dict

OpenAPI components (updated by this function)

required
paths dict

OpenAPI paths (updated by this function)

required
name str

Name of the entity

required
model dict

Schema of entity

required
route str

API path

required
_id str

Primary key to access identity by id

required
crud Union[Literal['C', 'R'], Literal['C', 'U'], Literal['C', 'D'], Literal['R', 'C'], Literal['R', 'U'], Literal['R', 'D'], Literal['U', 'C'], Literal['U', 'R'], Literal['U', 'D'], Literal['D', 'C'], Literal['D', 'R'], Literal['D', 'U'], Literal['C', 'R', 'U'], Literal['C', 'R', 'D'], Literal['C', 'U', 'R'], Literal['C', 'U', 'D'], Literal['C', 'D', 'R'], Literal['C', 'D', 'U'], Literal['R', 'C', 'U'], Literal['R', 'C', 'D'], Literal['R', 'U', 'C'], Literal['R', 'U', 'D'], Literal['R', 'D', 'C'], Literal['R', 'D', 'U'], Literal['U', 'C', 'R'], Literal['U', 'C', 'D'], Literal['U', 'R', 'C'], Literal['U', 'R', 'D'], Literal['U', 'D', 'C'], Literal['U', 'D', 'R'], Literal['D', 'C', 'R'], Literal['D', 'C', 'U'], Literal['D', 'R', 'C'], Literal['D', 'R', 'U'], Literal['D', 'U', 'C'], Literal['D', 'U', 'R']]

(C)reate (R)ead (U)pdate (D)elete, like "CRUD" for all or "CD" for "Create" and "Delete"

required
Source code in cdd/compound/openapi/utils/emit_openapi_utils.py
def components_paths_from_name_model_route_id_crud(
    components, paths, name, model, route, _id, crud
):
    """
    Update `components` and `paths` from `(name, model, route, _id, crud)`

    :param components: OpenAPI components (updated by this function)
    :type components: ```dict```

    :param paths: OpenAPI paths (updated by this function)
    :type paths: ```dict```

    :param name: Name of the entity
    :type name: ```str```

    :param model: Schema of entity
    :type model: ```dict```

    :param route: API path
    :type route: ```str```

    :param _id: Primary key to access identity by id
    :type _id: ```str```

    :param crud: (C)reate (R)ead (U)pdate (D)elete, like "CRUD" for all or "CD" for "Create" and "Delete"
    :type crud: ```Union[Literal['C', 'R'], Literal['C', 'U'], Literal['C', 'D'], Literal['R', 'C'],
                         Literal['R', 'U'], Literal['R', 'D'], Literal['U', 'C'], Literal['U', 'R'],
                         Literal['U', 'D'], Literal['D', 'C'], Literal['D', 'R'], Literal['D', 'U'],
                         Literal['C', 'R', 'U'], Literal['C', 'R', 'D'], Literal['C', 'U', 'R'],
                         Literal['C', 'U', 'D'], Literal['C', 'D', 'R'], Literal['C', 'D', 'U'],
                         Literal['R', 'C', 'U'], Literal['R', 'C', 'D'], Literal['R', 'U', 'C'],
                         Literal['R', 'U', 'D'], Literal['R', 'D', 'C'], Literal['R', 'D', 'U'],
                         Literal['U', 'C', 'R'], Literal['U', 'C', 'D'], Literal['U', 'R', 'C'],
                         Literal['U', 'R', 'D'], Literal['U', 'D', 'C'], Literal['U', 'D', 'R'],
                         Literal['D', 'C', 'R'], Literal['D', 'C', 'U'], Literal['D', 'R', 'C'],
                         Literal['D', 'R', 'U'], Literal['D', 'U', 'C'], Literal['D', 'U', 'R']]```
    """
    _request_body: bool = False
    if "C" in crud:
        paths[route] = {
            "post": {
                "summary": "A `{name}` object.".format(name=name),
                "requestBody": {
                    "required": True,
                    "$ref": "#/components/requestBodies/{name}Body".format(name=name),
                },
                "responses": {
                    "201": {
                        "description": "A `{name}` object.".format(name=name),
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/{name}".format(
                                        name=name
                                    )
                                }
                            }
                        },
                    },
                    "400": {
                        "description": "A `ServerError` object.",
                        "content": {
                            "application/json": {
                                "schema": {"$ref": "#/components/schemas/ServerError"}
                            }
                        },
                    },
                },
            }
        }
        _request_body: bool = True
    if not frozenset(crud) - frozenset("CRUD"):
        _route: str = "{route}/{{{id}}}".format(route=route, id=_id)
        paths[_route] = {
            "parameters": [
                {
                    "name": _id,
                    "in": "path",
                    "description": "Primary key of target `{name}`".format(name=name),
                    "required": True,
                    "schema": {"type": "string"},
                }
            ]
        }
        if "R" in crud:
            paths[_route]["get"] = {
                "summary": "A `{name}` object.".format(name=name),
                "responses": {
                    "200": {
                        "description": "A `{name}` object.".format(name=name),
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/{name}".format(
                                        name=name
                                    )
                                }
                            }
                        },
                    },
                    "404": {
                        "description": "A `ServerError` object.",
                        "content": {
                            "application/json": {
                                "schema": {"$ref": "#/components/schemas/ServerError"}
                            }
                        },
                    },
                },
            }

        # if "U" in crud:
        #     _request_body = True
        #     raise NotImplementedError(
        #         "UPDATE: https://github.com/sqlalchemy/sqlalchemy/discussions/5940"
        #     )

        if "D" in crud:
            paths[_route]["delete"] = {
                "summary": "Delete one `{name}`".format(name=name),
                "responses": {"204": {}},
            }
    components["schemas"][name] = {
        k: v for k, v in model.items() if not k.startswith("$")
    }
    if _request_body:
        components["requestBodies"]["{name}Body".format(name=name)] = {
            "description": "A `{name}` object.".format(name=name),
            "required": True,
            "content": {
                "application/json": {
                    "schema": {"$ref": "#/components/schemas/{name}".format(name=name)}
                }
            },
        }

cdd.compound.openapi.utils.emit_utils

cdd.compound.openapi.utils.emit_utils

Utility functions for cdd.emit.sqlalchemy

Parameters:

Name Type Description Default

cdd.compound.openapi.utils.parse_utils

cdd.compound.openapi.utils.parse_utils

Utility functions for cdd.parse.openapi

Parameters:

Name Type Description Default

extract_entities

extract_entities(openapi_str)

Extract entities from an OpenAPI string, where entities are defines as anything within "```"

Parameters:

Name Type Description Default
openapi_str str

The OpenAPI str

required

Returns:

Name Type Description
return_type list[str]

Entities

Source code in cdd/compound/openapi/utils/parse_utils.py
def extract_entities(openapi_str):
    """
    Extract entities from an OpenAPI string, where entities are defines as anything within "```"

    :param openapi_str: The OpenAPI str
    :type openapi_str: ```str```

    :return: Entities
    :rtype: ```list[str]```
    """
    entities, ticks, space, stack = [], 0, 0, []

    def add_then_clear_stack():
        """
        Join entity, if non-empty add to entities. Clear stack.
        """
        entity: str = "".join(stack)
        if entity:
            entities.append(entity)
        stack.clear()

    for idx, ch in enumerate(openapi_str):
        if ch.isspace():
            space += 1
            add_then_clear_stack()
            ticks: int = 0
        elif ticks > 2:
            ticks, space = 0, 0
            stack and add_then_clear_stack()
            stack.append(ch)
        elif ch == "`":
            ticks += 1
        elif stack:
            stack.append(ch)
    add_then_clear_stack()
    return entities

cdd.compound.sync_properties

cdd.compound.sync_properties

Functionality to synchronise properties

Parameters:

Name Type Description Default

sync_properties

sync_properties(input_eval, input_filename, input_params, output_filename, output_params, output_param_wrap=None)

Sync one property, inline to a file

Parameters:

Name Type Description Default
input_eval bool

Whether to evaluate the param, or just leave it

required
input_filename str

Filename to find param from

required
input_params list[str]

Locations within file of properties. Can be top level like ['a'] for a=5 or with the . syntax as in output_params.

required
output_filename str

Filename that will be edited in place, the property within this file (to update) is selected by output_param

required
output_params list[str]

Parameters to update. E.g., ['A.F'] for class A: F = None, ['f.g'] for def f(g): pass

required
output_param_wrap

Optional[str]

required
Source code in cdd/compound/sync_properties.py
def sync_properties(
    input_eval,
    input_filename,
    input_params,
    output_filename,
    output_params,
    output_param_wrap=None,
):
    """
    Sync one property, inline to a file

    :param input_eval: Whether to evaluate the `param`, or just leave it
    :type input_eval: ```bool```

    :param input_filename: Filename to find `param` from
    :type input_filename: ```str```

    :param input_params: Locations within file of properties.
       Can be top level like `['a']` for `a=5` or with the `.` syntax as in `output_params`.
    :type input_params: ```list[str]```

    :param output_filename: Filename that will be edited in place, the property within this file (to update)
     is selected by `output_param`
    :type output_filename: ```str```

    :param output_params: Parameters to update. E.g., `['A.F']` for `class A: F = None`, `['f.g']` for `def f(g): pass`
    :type output_params: ```list[str]```

    :param output_param_wrap: Wrap all input_str params with this. E.g., `Optional[Union[{output_param}, str]]`
    :param output_param_wrap: ```Optional[str]```
    """
    with open(path.realpath(path.expanduser(input_filename)), "rt") as f:
        input_ast = ast_parse(f.read(), filename=input_filename)

    with open(path.realpath(path.expanduser(output_filename)), "rt") as f:
        output_ast = ast_parse(f.read(), filename=output_filename)

    assert len(input_params) == len(output_params)
    for input_param, output_param in zip(input_params, output_params):
        output_ast = sync_property(
            input_eval,
            input_param,
            input_ast,
            input_filename,
            output_param,
            output_param_wrap,
            output_ast,
        )

    cdd.shared.emit.file.file(output_ast, output_filename, mode="wt", skip_black=False)

sync_property

sync_property(input_eval, input_param, input_ast, input_filename, output_param, output_param_wrap, output_ast)

Sync a single property

Parameters:

Name Type Description Default
input_eval bool

Whether to evaluate the param, or just leave it

required
input_param list[str]

Location within file of property. Can be top level like 'a' for a=5 or with the . syntax as in output_params.

required
input_ast AST

AST of the input file

required
input_filename str

Filename of the input (used in eval)

required
output_param str

Parameters to update. E.g., 'A.F' for class A: F = None, 'f.g' for def f(g): pass

required
output_param_wrap

Optional[str]

required
output_ast AST

AST of the input file

required

Returns:

Name Type Description
return_type AST

New AST derived from output_ast

Source code in cdd/compound/sync_properties.py
def sync_property(
    input_eval,
    input_param,
    input_ast,
    input_filename,
    output_param,
    output_param_wrap,
    output_ast,
):
    """
    Sync a single property

    :param input_eval: Whether to evaluate the `param`, or just leave it
    :type input_eval: ```bool```

    :param input_param: Location within file of property.
       Can be top level like `'a'` for `a=5` or with the `.` syntax as in `output_params`.
    :type input_param: ```list[str]```

    :param input_ast: AST of the input file
    :type input_ast: ```AST```

    :param input_filename: Filename of the input (used in `eval`)
    :type input_filename: ```str```

    :param output_param: Parameters to update. E.g., `'A.F'` for `class A: F = None`, `'f.g'` for `def f(g): pass`
    :type output_param: ```str```

    :param output_param_wrap: Wrap all input_str params with this. E.g., `Optional[Union[{output_param}, str]]`
    :param output_param_wrap: ```Optional[str]```

    :param output_ast: AST of the input file
    :type output_ast: ```AST```

    :return: New AST derived from `output_ast`
    :rtype: ```AST```
    """
    search: List[str] = list(strip_split(output_param, "."))
    if input_eval:
        if input_param.count(".") != 0:
            raise NotImplementedError("Anything not on the top-level of the module")

        local = {}
        output = eval(compile(input_ast, filename=input_filename, mode="exec"), local)
        assert output is None
        replacement_node = ast.AnnAssign(
            annotation=it2literal(local[input_param]),
            simple=1,
            target=ast.Name(
                # input_param
                search[-1],
                ast.Store(),
            ),
            value=None,
            expr=None,
            expr_annotation=None,
            expr_target=None,
            col_offset=None,
            lineno=None,
        )
    else:
        assert isinstance(
            input_ast, ast.Module
        ), "Expected `Module` got `{type_name}`".format(
            type_name=type(input_ast).__name__
        )
        annotate_ancestry(input_ast)
        replacement_node = find_in_ast(list(strip_split(input_param, ".")), input_ast)

    assert replacement_node is not None
    if output_param_wrap is not None:
        if hasattr(replacement_node, "annotation"):
            if replacement_node.annotation is not None:
                replacement_node.annotation = (
                    ast.parse(
                        output_param_wrap.format(
                            output_param=to_code(replacement_node.annotation)
                        )
                    )
                    .body[0]
                    .value
                )
        else:
            raise NotImplementedError(type(replacement_node).__name__)

    rewrite_at_query: RewriteAtQuery = RewriteAtQuery(
        search=search,
        replacement_node=replacement_node,
    )

    gen_ast = rewrite_at_query.visit(output_ast)

    assert (
        rewrite_at_query.replaced is True
    ), "Failed to update with {replacement_node_str!r}".format(
        replacement_node_str=to_code(replacement_node)
    )
    return gen_ast

cdd.docstring

cdd.docstring

docstring parser and emitter utility module

Parameters:

Name Type Description Default

cdd.docstring.emit

cdd.docstring.emit

Docstring emitter.

Emits into these formats from the cdd_python common IR format: - ReST docstring format (Sphinx) - numpydoc docstring format - Google's docstring format

Parameters:

Name Type Description Default

docstring

docstring(intermediate_repr, docstring_format='rest', purpose='function', word_wrap=True, indent_level=0, emit_separating_tab=True, emit_types=True, emit_original_whitespace=False, emit_default_doc=True)

Converts an IR to a docstring

Parameters:

Name Type Description Default
intermediate_repr dict

a dictionary consistent with IntermediateRepr, defined as: ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any}) IntermediateRepr = TypedDict("IntermediateRepr", { "name": Optional[str], "type": Optional[str], "doc": Optional[str], "params": OrderedDict[str, ParamVal], "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]], })

required
docstring_format Literal['rest', 'numpydoc', 'google']

Format of docstring

required
purpose Literal['class', 'function']
required
if purpose == 'function' elif purpose == 'class' then

:paramif purpose == 'function' elif purpose == 'class' then

required
:cvar`

:cvar`

required
word_wrap bool

Whether to word-wrap. Set DOCTRANS_LINE_LENGTH to configure length.

required
indent_level int

indentation level whence: 0=no_tabs, 1=one tab; 2=two tabs

required
emit_separating_tab bool
required
and return and desc

:param and return and desc

required
emit_types bool
required
lines :type` lines
required
emit_original_whitespace bool

Whether to emit original whitespace or strip it out

required
emit_default_doc bool

Whether help/docstring should include 'With default' text

required

Returns:

Name Type Description
return_type str

docstring

Source code in cdd/docstring/emit.py
def docstring(
    intermediate_repr,
    docstring_format="rest",
    purpose="function",
    word_wrap=True,
    indent_level=0,
    emit_separating_tab=True,
    emit_types=True,
    emit_original_whitespace=False,
    emit_default_doc=True,
):
    """
    Converts an IR to a docstring

    :param intermediate_repr: a dictionary consistent with `IntermediateRepr`, defined as:
        ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any})
        IntermediateRepr = TypedDict("IntermediateRepr", {
            "name": Optional[str],
            "type": Optional[str],
            "doc": Optional[str],
            "params": OrderedDict[str, ParamVal],
            "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]],
        })
    :type intermediate_repr: ```dict```

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

    :param purpose: Emit `:param` if purpose == 'function' elif purpose == 'class' then `:cvar`
    :type purpose: ```Literal['class', 'function']```

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

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

    :param emit_separating_tab: Whether to put a tab between :param and return and desc
    :type emit_separating_tab: ```bool```

    :param emit_types: Whether to show `:type` lines
    :type emit_types: ```bool```

    :param emit_original_whitespace: Whether to emit original whitespace or strip it out
    :type emit_original_whitespace: ```bool```

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

    :return: docstring
    :rtype: ```str```
    """
    # _sep = tab * indent_level
    params = "\n{maybe_nl}".format(
        maybe_nl="\n" if docstring_format == "rest" and purpose != "class" else ""
    ).join(
        (
            lambda param_lines: (
                [getattr(ARG_TOKENS, docstring_format)[0]] + param_lines
                if param_lines and docstring_format != "rest"
                else param_lines
            )
        )(
            list(
                map(
                    partial(
                        emit_param_str,
                        style=docstring_format,
                        purpose=purpose,
                        emit_type=emit_types,
                        emit_default_doc=emit_default_doc,
                        word_wrap=word_wrap,
                    ),
                    (intermediate_repr["params"] or OrderedDict()).items(),
                ),
            )
        )
    )

    returns = (
        (
            lambda line_: (
                "".join(
                    "{maybe_nl0_and_token}{maybe_nl1}{returns_doc}".format(
                        maybe_nl0_and_token=(
                            ""
                            if docstring_format == "rest"
                            else "\n{return_token}".format(
                                return_token=getattr(RETURN_TOKENS, docstring_format)[0]
                            )
                        ),
                        maybe_nl1="" if not params or params[-1] == "\n" else "\n",
                        returns_doc=line_,
                    )
                )
                if line_
                else ""
            )
        )(
            next(
                map(
                    partial(
                        emit_param_str,
                        style=docstring_format,
                        purpose=purpose,
                        emit_type=emit_types,
                        emit_default_doc=emit_default_doc,
                        word_wrap=word_wrap,
                    ),
                    intermediate_repr["returns"].items(),
                ),
                None,
            )
        )
        if "return_type" in (intermediate_repr.get("returns") or iter(()))
        else ""
    )

    params_end_nls = num_of_nls(params, end=True)
    returns_end_nls = num_of_nls(returns, end=True)

    candidate_args_returns = "{params}{maybe_nl0}{returns}{maybe_nl1}".format(
        params=params,
        maybe_nl0="\n" if params_end_nls < 2 and returns else "",
        returns=returns,
        maybe_nl1=(
            "\n"
            if not returns and params_end_nls > 0 or returns and returns_end_nls == 0
            else ""
        ),
    )

    original_doc_str: str = intermediate_repr.get("_internal", {}).get(
        "original_doc_str", ""
    )
    if original_doc_str:
        header, _, footer = parse_docstring_into_header_args_footer(
            candidate_args_returns, original_doc_str
        )
        header = (
            intermediate_repr.get("doc", "") if not header and not footer else header
        )
    else:
        header, footer = intermediate_repr.get("doc", ""), ""

    candidate_doc_str: str = header_args_footer_to_str(
        header=header,
        args_returns="" if candidate_args_returns.isspace() else candidate_args_returns,
        footer=footer,
    )

    if not candidate_doc_str or candidate_doc_str.isspace():
        return ""

    prev_nl, next_nl = 0, candidate_doc_str.find("\n")
    current_indent, line = 0, None

    # One line only
    if next_nl == -1:
        # current_indent:int = count_iter_items(takewhile(str.isspace, candidate_doc_str))
        # _sep = (indent_level - current_indent) * tab
        return (
            candidate_doc_str
            if candidate_doc_str[0] == "\n"
            else "\n{_sep}{candidate_doc_str}".format(
                _sep="", candidate_doc_str=candidate_doc_str
            )
        )
    else:
        # Ignore starting newlines/whitespace only lines, keep munching until last line
        while next_nl > -1:
            line = candidate_doc_str[prev_nl:next_nl]
            if not line.isspace():
                break
            # prev_nl = next_nl
            # current_indent:int = count_iter_items(takewhile(str.isspace, line))

    if indent_level > current_indent:
        _tab = (indent_level - current_indent) * tab
        lines = ([line] if line else []) + candidate_doc_str[
            (
                next_nl
                if len(candidate_doc_str) == next_nl
                or next_nl + 1 < len(candidate_doc_str)
                and candidate_doc_str[next_nl + 1] != "\n"
                else next_nl + 1
            ) :
        ].splitlines()
        candidate_doc_str: str = "\n".join(
            map(
                lambda _line: (
                    "{_tab}{_line}".format(_tab=_tab, _line=_line)
                    if _line or emit_separating_tab
                    # and not _line.startswith(_tab)
                    else _line
                ),
                lines,
            )
        )
        if len(lines) > 1:
            candidate_doc_str: str = (
                "{maybe_nl}{candidate_doc_str}{maybe_nl_tab}".format(
                    maybe_nl="\n" if candidate_doc_str.startswith(_tab) else "",
                    candidate_doc_str=candidate_doc_str,
                    maybe_nl_tab=(
                        ""
                        if candidate_doc_str[-1] == "\n"
                        else "\n{_tab}".format(_tab=_tab)
                    ),
                )
            )

    return candidate_doc_str

cdd.docstring.parse

cdd.docstring.parse

Docstring parser

Parameters:

Name Type Description Default

docstring

docstring(doc_string, infer_type=False, return_tuple=False, parse_original_whitespace=False, emit_default_prop=True, emit_default_doc=True)

Converts a docstring to an AST

Parameters:

Name Type Description Default
doc_string Union[str, Dict]

docstring portion

required
infer_type bool

Whether to try inferring the typ (from the default)

required
return_tuple Tuple

Whether to return a tuple, or just the intermediate_repr

required
parse_original_whitespace bool

Whether to parse original whitespace or strip it out

required
emit_default_prop dict

Whether to include the default dictionary property.

required
emit_default_doc bool

Whether help/docstring should include 'With default' text

required

Returns:

Name Type Description
return_type Optional[Union[dict, Tuple[dict, bool]]]

intermediate_repr, whether it returns or not

Source code in cdd/docstring/parse.py
def docstring(
    doc_string,
    infer_type=False,
    return_tuple=False,
    parse_original_whitespace=False,
    emit_default_prop=True,
    emit_default_doc=True,
):
    """
    Converts a docstring to an AST

    :param doc_string: docstring portion
    :type doc_string: ```Union[str, Dict]```

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

    :param return_tuple: Whether to return a tuple, or just the intermediate_repr
    :type return_tuple: ```bool```

    :param parse_original_whitespace: Whether to parse original whitespace or strip it out
    :type parse_original_whitespace: ```bool```

    :param emit_default_prop: Whether to include the default dictionary property.
    :type emit_default_prop: ```bool```

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

    :return: intermediate_repr, whether it returns or not
    :rtype: ```Optional[Union[dict, Tuple[dict, bool]]]```
    """
    assert isinstance(
        doc_string, str
    ), "Expected `str` got `{doc_string_type!r}`".format(
        doc_string_type=type(doc_string).__name__
    )
    parsed: IntermediateRepr = (
        doc_string
        if isinstance(doc_string, dict)
        else cdd.shared.docstring_parsers.parse_docstring(
            doc_string,
            infer_type=infer_type,
            emit_default_prop=emit_default_prop,
            emit_default_doc=emit_default_doc,
            parse_original_whitespace=parse_original_whitespace,
        )
    )

    if return_tuple:
        return parsed, (
            "returns" in parsed
            and parsed["returns"] is not None
            and "return_type" in (parsed.get("returns") or iter(()))
        )

    return parsed

cdd.docstring.utils

cdd.docstring.utils

docstring parser and emitter utility module

Parameters:

Name Type Description Default

cdd.docstring.utils.emit_utils

cdd.docstring.utils.emit_utils

Utility functions for cdd.emit.docstring

Parameters:

Name Type Description Default

interpolate_defaults

interpolate_defaults(param, default_search_announce=None, require_default=False, emit_default_doc=True)

Correctly set the 'default' and 'doc' parameters

Parameters:

Name Type Description Default
param dict

Name, dict with keys: 'typ', 'doc', 'default'

required
default_search_announce Optional[Union[str, Iterable[str]]]

Default text(s) to look for. If None, uses default specified in default_utils.

required
require_default bool

Whether a default is required, if not found in doc, infer the proper default from type

required
emit_default_doc bool

Whether help/docstring should include 'With default' text

required

Returns:

Name Type Description
return_type tuple[str, dict]

Name, dict with keys: 'typ', 'doc', 'default'

Source code in cdd/docstring/utils/emit_utils.py
def interpolate_defaults(
    param, default_search_announce=None, require_default=False, emit_default_doc=True
):
    """
    Correctly set the 'default' and 'doc' parameters

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

    :param default_search_announce: Default text(s) to look for. If None, uses default specified in default_utils.
    :type default_search_announce: ```Optional[Union[str, Iterable[str]]]```

    :param require_default: Whether a default is required, if not found in doc, infer the proper default from type
    :type require_default: ```bool```

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

    :return: Name, dict with keys: 'typ', 'doc', 'default'
    :rtype: ```tuple[str, dict]```
    """
    name, _param = param
    del param
    if "doc" in _param:
        doc, default = extract_default(
            _param["doc"],
            typ=_param.get("typ"),
            default_search_announce=default_search_announce,
            emit_default_doc=emit_default_doc,
        )
        _param["doc"] = doc
        if default is not None:
            _param["default"] = unquote(default)
    if require_default and _param.get("default") is None:
        # if (
        #     "typ" in _param
        #     and _param["typ"] not in frozenset(("Any", "object"))
        #     and not _param["typ"].startswith("Optional")
        # ):
        #     _param["typ"] = "Optional[{}]".format(_param["typ"])
        _param["default"] = (
            simple_types[_param["typ"]]
            if _param.get("typ", memoryview) in simple_types
            else cdd.shared.ast_utils.NoneStr
        )

    return name, _param

cdd.docstring.utils.parse_utils

cdd.docstring.utils.parse_utils

Docstring parse utils

Parameters:

Name Type Description Default

parse_adhoc_doc_for_typ

parse_adhoc_doc_for_typ(doc, name, default_is_none)

Google's Keras and other frameworks have an adhoc syntax.

Call this function after the first-pass; i.e., after the arg {name, doc, typ, default} are 'known'.

Parameters:

Name Type Description Default
doc str

Possibly ambiguous docstring for argument, that might hint as to the type

required
name str

Name of argument; useful for debugging and if the name hints as to the type

required
default_is_none bool

Whether the default is NoneStr

required

Returns:

Name Type Description
return_type Optional[str]

The type (if determined) else None

Source code in cdd/docstring/utils/parse_utils.py
def parse_adhoc_doc_for_typ(doc, name, default_is_none):
    """
    Google's Keras and other frameworks have an adhoc syntax.

    Call this function after the first-pass; i.e., after the arg {name, doc, typ, default} are 'known'.

    :param doc: Possibly ambiguous docstring for argument, that *might* hint as to the type
    :type doc: ```str```

    :param name: Name of argument; useful for debugging and if the name hints as to the type
    :type name: ```str```

    :param default_is_none: Whether the default is `NoneStr`
    :type default_is_none: ```bool```

    :return: The type (if determined) else `None`
    :rtype: ```Optional[str]```
    """
    if not doc:
        return None

    wrap: str = "Optional[{}]" if default_is_none else "{}"

    words: List[Union[List[str], str]] = [[]]
    candidate_type, fst_sentence, sentence = _parse_adhoc_doc_for_typ_phase0(doc, words)

    if sentence is not None:
        sentence, wrap_type_with = _parse_adhoc_doc_for_typ_phase1(sentence, words)

        new_candidate_type: Optional[str] = cast(
            Optional[str], _union_literal_from_sentence(sentence)
        )
        if new_candidate_type is not None:
            if (
                new_candidate_type.startswith("Literal[")
                and candidate_type in simple_types
                and candidate_type is not None
            ):
                wrap_type_with = "Union[{}, " + "{}]".format(candidate_type)
            candidate_type: str = (
                new_candidate_type[len("Union[") : -len("]")]
                if wrap_type_with == "Mapping[{}]"
                else new_candidate_type
            )
        if candidate_type is not None:
            return wrap_type_with.format(candidate_type)

    if fst_sentence is not None:
        whole_sentence_as_type: Optional[str] = type_to_name.get(
            fst_sentence.rstrip(".")
        )
        if whole_sentence_as_type is not None:
            return whole_sentence_as_type
    if candidate_type is not None:
        return candidate_type
    elif len(words) > 2:
        if "/" in words[2]:
            return "Union[{}]".format(",".join(deduplicate(words[2].split("/"))))
        candidate_type: Optional[str] = next(
            map(
                adhoc_3_tuple_to_type.__getitem__,
                filter(
                    partial(contains, adhoc_3_tuple_to_type),
                    sliding_window(words, 3),
                ),
            ),
            None,
        )

    return candidate_type if candidate_type is None else wrap.format(candidate_type)

cdd.function

cdd.function

function parser and emitter module

Parameters:

Name Type Description Default

cdd.function.emit

cdd.function.emit

Function/method emitter

Parameters:

Name Type Description Default

function

function(intermediate_repr, function_name, function_type, word_wrap=True, emit_default_doc=False, docstring_format='rest', indent_level=2, emit_separating_tab=PY3_8, type_annotations=True, emit_as_kwonlyargs=True, emit_original_whitespace=False)

Construct a function from our IR

Parameters:

Name Type Description Default
intermediate_repr dict

a dictionary consistent with IntermediateRepr, defined as: ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any}) IntermediateRepr = TypedDict("IntermediateRepr", { "name": Optional[str], "type": Optional[str], "doc": Optional[str], "params": OrderedDict[str, ParamVal], "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]], })

required
function_name name

name of function_def

required
function_type Optional[Literal['self', 'cls', 'static']]

Type of function, static is static or global method, others just become first arg

required
docstring_format Literal['rest', 'numpydoc', 'google']

Format of docstring

required
word_wrap bool

Whether to word-wrap. Set DOCTRANS_LINE_LENGTH to configure length.

required
emit_default_doc bool

Whether help/docstring should include 'With default' text

required
indent_level int

docstring indentation level whence: 0=no_tabs, 1=one tab; 2=two tabs

required
emit_separating_tab bool
required
and return and desc

:param and return and desc

required
type_annotations bool

True to have type annotations (3.6+), False to place in docstring

required
emit_as_kwonlyargs bool

Whether argument(s) emitted must be keyword only

required
emit_original_whitespace bool

Whether to emit an original whitespace (in docstring) or strip it out

required

Returns:

Name Type Description
return_type FunctionDef

AST node for function definition

Source code in cdd/function/emit.py
def function(
    intermediate_repr,
    function_name,
    function_type,
    word_wrap=True,
    emit_default_doc=False,
    docstring_format="rest",
    indent_level=2,
    emit_separating_tab=PY3_8,
    type_annotations=True,
    emit_as_kwonlyargs=True,
    emit_original_whitespace=False,
):
    """
    Construct a function from our IR

    :param intermediate_repr: a dictionary consistent with `IntermediateRepr`, defined as:
        ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any})
        IntermediateRepr = TypedDict("IntermediateRepr", {
            "name": Optional[str],
            "type": Optional[str],
            "doc": Optional[str],
            "params": OrderedDict[str, ParamVal],
            "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]],
        })
    :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', 'numpydoc', 'google']```

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

    :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 type_annotations: True to have type annotations (3.6+), False to place in docstring
    :type type_annotations: ```bool```

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

    :param emit_original_whitespace: Whether to emit an original whitespace (in docstring) or strip it out
    :type emit_original_whitespace: ```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: Optional[str] = function_name or intermediate_repr["name"]
    function_type: Optional[str] = function_type or intermediate_repr["type"]

    args = (
        []
        if function_type in frozenset((None, "static"))
        else [cdd.shared.ast_utils.set_arg(function_type)]
    )
    from cdd.shared.emit.utils.emitter_utils import ast_parse_fix

    args_from_params = list(
        map(
            lambda param: cdd.shared.ast_utils.set_arg(
                annotation=(
                    (
                        Name(param[1]["typ"], Load(), lineno=None, col_offset=None)
                        if param[1]["typ"] in simple_types
                        else ast_parse_fix(param[1]["typ"])
                    )
                    if type_annotations and "typ" in param[1]
                    else None
                ),
                arg=param[0],
            ),
            params_no_kwargs,
        ),
    )
    defaults_from_params = list(
        map(
            lambda param: (
                cdd.shared.ast_utils.set_value(None)
                if param[1].get("default") in none_types
                else cdd.shared.ast_utils.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: Internal = 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: cdd.shared.ast_utils.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(
                        cdd.shared.ast_utils.set_value(
                            docstring(
                                intermediate_repr,
                                docstring_format=docstring_format,
                                emit_default_doc=emit_default_doc,
                                emit_original_whitespace=emit_original_whitespace,
                                emit_separating_tab=emit_separating_tab,
                                emit_types=not type_annotations,
                                indent_level=indent_level,
                                word_wrap=word_wrap,
                            )
                        ),
                        lineno=None,
                        col_offset=None,
                    ),
                    *(
                        internal_body[:-1]
                        if internal_body
                        and isinstance(internal_body[-1], Return)
                        and return_val
                        else internal_body
                    ),
                    return_val,
                ),
            )
        ),
        decorator_list=[],
        type_params=[],
        name=function_name,
        returns=(
            ast.parse(intermediate_repr["returns"]["return_type"]["typ"]).body[0].value
            if type_annotations
            and (intermediate_repr.get("returns") or {"return_type": {}})[
                "return_type"
            ].get("typ")
            else None
        ),
        lineno=None,
        arguments_args=None,
        identifier_name=None,
        stmt=None,
        **cdd.shared.ast_utils.maybe_type_comment
    )

cdd.function.parse

cdd.function.parse

Function parser

Parameters:

Name Type Description Default

function

function(function_def, infer_type=False, parse_original_whitespace=False, word_wrap=True, function_type=None, function_name=None)

Converts a method to our IR

Parameters:

Name Type Description Default
function_def Union[FunctionDef, FunctionType]

AST node for function definition

required
infer_type bool

Whether to try inferring the typ (from the default)

required
parse_original_whitespace bool

Whether to parse original whitespace or strip it out

required
word_wrap bool

Whether to word-wrap. Set DOCTRANS_LINE_LENGTH to configure length.

required
function_type Literal['self', 'cls', 'static']

Type of function, static is static or global method, others just become first arg

required
function_name name

name of function_def

required

Returns:

Name Type Description
return_type dict

a dictionary consistent with IntermediateRepr, defined as: ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any}) IntermediateRepr = TypedDict("IntermediateRepr", { "name": Optional[str], "type": Optional[str], "doc": Optional[str], "params": OrderedDict[str, ParamVal], "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]], })

Source code in cdd/function/parse.py
def function(
    function_def,
    infer_type=False,
    parse_original_whitespace=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 parse_original_whitespace: Whether to parse original whitespace or strip it out
    :type parse_original_whitespace: ```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: ```Optional[str]```

    :return: a dictionary consistent with `IntermediateRepr`, defined as:
        ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any})
        IntermediateRepr = TypedDict("IntermediateRepr", {
            "name": Optional[str],
            "type": Optional[str],
            "doc": Optional[str],
            "params": OrderedDict[str, ParamVal],
            "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]],
        })
    :rtype: ```dict```
    """
    if isinstance(function_def, FunctionType):
        # Dynamic function, i.e., this isn't source code; and is in your memory
        ir: IntermediateRepr = cdd.shared.parse.utils.parser_utils._inspect(
            function_def,
            function_name,
            parse_original_whitespace=parse_original_whitespace,
            word_wrap=word_wrap,
        )
        parsed_source: FunctionDef = cast(
            FunctionDef, ast.parse(getsource(function_def).lstrip()).body[0]
        )
        original_doc_str: Optional[str] = ast.get_docstring(
            parsed_source, clean=parse_original_whitespace
        )
        body: FunctionDef.body = (
            parsed_source.body if original_doc_str is None else parsed_source.body[1:]
        )
        ir["_internal"] = {
            "original_doc_str": (
                original_doc_str
                if parse_original_whitespace
                else ast.get_docstring(parsed_source, clean=False)
            ),
            "body": cast(
                List[AST],
                list(filterfalse(rpartial(isinstance, (AnnAssign, Assign)), 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 = cdd.shared.ast_utils.get_function_type(function_def)

    # Read docstring
    doc_str: Optional[str] = (
        get_docstring(function_def, clean=parse_original_whitespace)
        if isinstance(function_def, FunctionDef)
        else None
    )

    function_def: FunctionDef = 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: IntermediateRepr = {
            "name": function_name or function_def.name,
            "params": OrderedDict(),
            "returns": None,
            "_internal": {},
        }
    else:
        intermediate_repr: IntermediateRepr = cdd.docstring.parse.docstring(
            doc_str.replace(":cvar", ":param"),
            parse_original_whitespace=parse_original_whitespace,
            infer_type=infer_type,
        )
        intermediate_repr["_internal"] = {
            "original_doc_str": (
                doc_str
                if parse_original_whitespace
                else (
                    get_docstring(function_def, clean=False)
                    if isinstance(function_def, FunctionDef)
                    else None
                )
            )
        }

    intermediate_repr.update(
        {
            "name": function_name or function_def.name,
            "type": function_type or found_type,
        }
    )

    intermediate_repr["_internal"].update(
        {
            "from_name": function_def.name,
            "from_type": 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

    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"] = cdd.shared.ast_utils.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 = abs(
            len(getattr(function_def.args, args))
            - len(getattr(function_def.args, defaults))
        )
        if diff:
            setattr(
                function_def.args,
                defaults,
                list(islice(cycle((None,)), diff))
                + getattr(function_def.args, defaults),
            )
    cdd.shared.parse.utils.parser_utils.ir_merge(
        intermediate_repr,
        {
            "params": OrderedDict(
                (
                    cdd.shared.ast_utils.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(
                cdd.shared.docstring_parsers._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: IntermediateRepr = _interpolate_return(
        function_def, intermediate_repr
    )
    if "return_type" in (intermediate_repr.get("returns") or iter(())):
        intermediate_repr["returns"] = OrderedDict(
            map(
                partial(
                    cdd.shared.docstring_parsers._set_name_and_type,
                    infer_type=infer_type,
                    word_wrap=word_wrap,
                ),
                intermediate_repr["returns"].items(),
            )
        )
    return intermediate_repr

cdd.function.utils

cdd.function.utils

function parser and emitter utility module

Parameters:

Name Type Description Default

cdd.function.utils.emit_utils

cdd.function.utils.emit_utils

Utility functions for cdd.emit.function_utils

Parameters:

Name Type Description Default

make_call_meth

make_call_meth(body, return_type, param_names, docstring_format, word_wrap)

Construct a __call__ method from the provided body

Parameters:

Name Type Description Default
body list[AST]

The body, probably from a FunctionDef.body

required
return_type Optional[str]

The return type of the parent symbol (probably class). Used to fill in __call__ return.

required
param_names id

Container of AST ids to match for rename

required
docstring_format Literal['rest', 'numpydoc', 'google']

Format of docstring

required
word_wrap bool

Whether to word-wrap. Set DOCTRANS_LINE_LENGTH to configure length.

required

Returns:

Name Type Description
return_type FunctionDef

Internal function for __call__

Source code in cdd/function/utils/emit_utils.py
def make_call_meth(body, return_type, param_names, docstring_format, word_wrap):
    """
    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]]```

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

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

    :return: Internal function for `__call__`
    :rtype: ```FunctionDef```
    """
    body_len: int = len(body)
    if body_len and isinstance(body, dict):
        body = list(
            filter(
                None,
                (
                    (
                        None
                        if body.get("doc") in none_types
                        else Expr(
                            cdd.shared.ast_utils.set_value(
                                emit_param_str(
                                    (
                                        "return_type",
                                        {
                                            "doc": multiline(
                                                indent_all_but_first(body["doc"])
                                            )
                                        },
                                    ),
                                    style=docstring_format,
                                    word_wrap=word_wrap,
                                    purpose="function",
                                )
                            ),
                            lineno=None,
                            col_offset=None,
                        )
                    ),
                    (
                        RewriteName(param_names).visit(
                            Return(
                                cdd.shared.ast_utils.get_value(
                                    ast.parse(return_type.strip("`")).body[0]
                                ),
                                expr=None,
                            )
                        )
                        if code_quoted(body["default"])
                        else Return(
                            cdd.shared.ast_utils.set_value(body["default"]), expr=None
                        )
                    ),
                ),
            )
        )

    return (
        ast.fix_missing_locations(
            FunctionDef(
                args=arguments(
                    args=[cdd.shared.ast_utils.set_arg("self")],
                    defaults=[],
                    kw_defaults=[],
                    kwarg=None,
                    kwonlyargs=[],
                    posonlyargs=[],
                    vararg=None,
                    arg=None,
                ),
                body=body,
                decorator_list=[],
                type_params=[],
                name="__call__",
                returns=None,
                arguments_args=None,
                identifier_name=None,
                stmt=None,
                lineno=None,
                **cdd.shared.ast_utils.maybe_type_comment
            )
        )
        if body
        else None
    )

cdd.function.utils.parse_utils

cdd.function.utils.parse_utils

Utility functions for cdd.parse.function

Parameters:

Name Type Description Default

cdd.json_schema

cdd.json_schema

JSON-schema parser and emitter utility module

Parameters:

Name Type Description Default

cdd.json_schema.emit

cdd.json_schema.emit

JSON schema emitter

Parameters:

Name Type Description Default

json_schema

json_schema(intermediate_repr, identifier=None, emit_original_whitespace=False, emit_default_doc=False, word_wrap=False)

Construct a JSON schema dict

Parameters:

Name Type Description Default
intermediate_repr dict

a dictionary consistent with IntermediateRepr, defined as: ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any}) IntermediateRepr = TypedDict("IntermediateRepr", { "name": Optional[str], "type": Optional[str], "doc": Optional[str], "params": OrderedDict[str, ParamVal], "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]], })

required
identifier id

The $id of the schema

required
emit_original_whitespace bool

Whether to emit original whitespace (in top-level description) or strip it out

required
emit_default_doc bool

Whether help/docstring should include 'With default' text

required
word_wrap bool

Whether to word-wrap. Set DOCTRANS_LINE_LENGTH to configure length.

required

Returns:

Name Type Description
return_type dict

JSON Schema dict

Source code in cdd/json_schema/emit.py
def json_schema(
    intermediate_repr,
    identifier=None,
    emit_original_whitespace=False,
    emit_default_doc=False,
    word_wrap=False,
):
    """
    Construct a JSON schema dict

    :param intermediate_repr: a dictionary consistent with `IntermediateRepr`, defined as:
        ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any})
        IntermediateRepr = TypedDict("IntermediateRepr", {
            "name": Optional[str],
            "type": Optional[str],
            "doc": Optional[str],
            "params": OrderedDict[str, ParamVal],
            "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]],
        })
    :type intermediate_repr: ```dict```

    :param identifier: The `$id` of the schema
    :type identifier: ```str```

    :param emit_original_whitespace: Whether to emit original whitespace (in top-level `description`) or strip it out
    :type emit_original_whitespace: ```bool```

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

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

    :return: JSON Schema dict
    :rtype: ```dict```
    """
    del emit_default_doc, word_wrap
    assert isinstance(
        intermediate_repr, dict
    ), "Expected `dict` got `{type_name}`".format(
        type_name=type(intermediate_repr).__name__
    )
    if "$id" in intermediate_repr and "params" not in intermediate_repr:
        return intermediate_repr  # Somehow this function got JSON schema as input
    if identifier is None:
        identifier: str = intermediate_repr.get(
            "$id",
            "https://offscale.io/{}.schema.json".format(
                intermediate_repr.get("name", "INFERRED")
            ),
        )
    required = []
    _param2json_schema_property = partial(param2json_schema_property, required=required)
    properties = dict(
        map(_param2json_schema_property, intermediate_repr["params"].items())
    )

    return {
        "$id": identifier,
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "description": (
            deindent(
                add(
                    *map(
                        partial(
                            docstring,
                            emit_default_doc=True,
                            emit_original_whitespace=emit_original_whitespace,
                            emit_types=True,
                        ),
                        (
                            {
                                "doc": intermediate_repr["doc"],
                                "params": OrderedDict(),
                                "returns": None,
                            },
                            {
                                "doc": "",
                                "params": OrderedDict(),
                                "returns": intermediate_repr["returns"],
                            },
                        ),
                    )
                )
            ).lstrip("\n")
            or None
        ),
        "type": "object",
        "properties": properties,
        "required": required,
    }

json_schema_file

json_schema_file(input_mapping, output_filename)

Emit input_mapping—as JSON schema—into output_filename

Parameters:

Name Type Description Default
input_mapping Dict[str, AST]

Import location of mapping/2-tuple collection.

required
output_filename str

Output file to write to

required
Source code in cdd/json_schema/emit.py
def json_schema_file(input_mapping, output_filename):
    """
    Emit `input_mapping`—as JSON schema—into `output_filename`

    :param input_mapping: Import location of mapping/2-tuple collection.
    :type input_mapping: ```Dict[str, AST]```

    :param output_filename: Output file to write to
    :type output_filename: ```str```
    """
    schemas_it = (json_schema(v) for k, v in input_mapping.items())
    schemas = (
        {"schemas": list(schemas_it)} if len(input_mapping) > 1 else next(schemas_it)
    )
    with open(output_filename, "a") as f:
        dump(schemas, f, cls=SetEncoder)

cdd.json_schema.parse

cdd.json_schema.parse

JSON schema parser

Parameters:

Name Type Description Default

json_schema

json_schema(json_schema_dict, parse_original_whitespace=False)

Parse a JSON schema into the IR

Parameters:

Name Type Description Default
json_schema_dict dict

A valid JSON schema as a Python dict

required
parse_original_whitespace bool

Whether to parse original whitespace or strip it out

required

Returns:

Name Type Description
return_type dict

IR representation of the given JSON schema

Source code in cdd/json_schema/parse.py
def json_schema(json_schema_dict, parse_original_whitespace=False):
    """
    Parse a JSON schema into the IR

    :param json_schema_dict: A valid JSON schema as a Python dict
    :type json_schema_dict: ```dict```

    :param parse_original_whitespace: Whether to parse original whitespace or strip it out
    :type parse_original_whitespace: ```bool```

    :return: IR representation of the given JSON schema
    :rtype: ```dict```
    """
    # I suppose a JSON-schema validation routine could be executed here
    schema: dict = deepcopy(json_schema_dict)

    required: FrozenSet[str] = (
        frozenset(schema["required"]) if schema.get("required") else frozenset()
    )
    _json_schema_property_to_param = partial(
        json_schema_property_to_param, required=required
    )

    ir: IntermediateRepr = docstring(
        json_schema_dict.get("description", ""),
        emit_default_doc=False,
        parse_original_whitespace=parse_original_whitespace,
    )
    ir.update(
        {
            "params": (
                OrderedDict(
                    map(_json_schema_property_to_param, schema["properties"].items())
                )
                if "properties" in schema
                else OrderedDict()
            ),
            "name": json_schema_dict.get(
                "name",
                json_schema_dict.get("id", json_schema_dict.get("title", ir["name"])),
            ),
        }
    )
    return ir

cdd.json_schema.utils

cdd.json_schema.utils

JSON-schema parser and emitter utility module

Parameters:

Name Type Description Default

cdd.json_schema.utils.emit_utils

cdd.json_schema.utils.emit_utils

Utility functions for cdd.emit.json_schema

Parameters:

Name Type Description Default

param2json_schema_property

param2json_schema_property(param, required)

Turn a param into a JSON schema property

Parameters:

Name Type Description Default
param dict

Name, dict with keys: 'typ', 'doc', 'default'

required
required list

Required parameters. This function may push to the list.

required

Returns:

Name Type Description
return_type dict

JSON schema property. Also, may push to required.

Source code in cdd/json_schema/utils/emit_utils.py
def param2json_schema_property(param, required):
    """
    Turn a param into a JSON schema property

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

    :param required: Required parameters. This function may push to the list.
    :type required: ```list[str]```

    :return: JSON schema property. Also, may push to `required`.
    :rtype: ```dict```
    """
    name, _param = param
    del param
    if _param.get("doc"):
        _param["description"] = _param.pop("doc")
    if _param.get("typ") == "datetime":
        del _param["typ"]
        _param.update({"type": "string", "format": "date-time"})
        required.append(name)
    elif _param.get("typ") in typ2json_type:
        _param["type"] = typ2json_type[_param.pop("typ")]
        required.append(name)
    elif _param.get("typ", ast) is not ast:
        _param["type"] = _param.pop("typ")
        if _param["type"].startswith("Optional["):
            _param["type"] = _param["type"][len("Optional[") : -1]
            if _param["type"] in typ2json_type:
                _param["type"] = typ2json_type[_param["type"]]
            # elif _param.get("typ") in typ2json_type:
            #    _param["type"] = typ2json_type[_param.pop("typ")]
        else:
            required.append(name)

        if _param["type"].startswith("Literal["):
            parsed_typ = cdd.shared.ast_utils.get_value(
                ast.parse(_param["type"]).body[0]
            )
            assert (
                parsed_typ.value.id == "Literal"
            ), "Only basic Literal support is implemented, not {}".format(
                parsed_typ.value.id
            )
            enum = sorted(
                map(
                    cdd.shared.ast_utils.get_value,
                    cdd.shared.ast_utils.get_value(parsed_typ.slice).elts,
                )
            )
            _param.update(
                {
                    "pattern": "|".join(enum),
                    "type": typ2json_type[type(enum[0]).__name__],
                }
            )
    if _param.get("default", False) in none_types:
        del _param["default"]  # Will be inferred as `null` from the type
    elif isinstance(_param.get("default"), AST):
        _param["default"] = cdd.shared.ast_utils.ast_type_to_python_type(
            _param["default"]
        )
    if isinstance(_param.get("choices"), Set):
        _param["pattern"] = "|".join(
            sorted(map(str, cdd.shared.ast_utils.Set_to_set(_param.pop("choices"))))
        )
    return name, _param

cdd.json_schema.utils.parse_utils

cdd.json_schema.utils.parse_utils

Utility functions for cdd.parse.json_schema

Parameters:

Name Type Description Default

json_schema_property_to_param

json_schema_property_to_param(param, required)

Convert a JSON schema property to a param

Parameters:

Name Type Description Default
param dict

Name, dict with keys: 'typ', 'doc', 'default'

required
required FrozenSet[str]

Names of all required parameters

required

Returns:

Name Type Description
return_type tuple[str, dict]

Name, dict with keys: 'typ', 'doc', 'default'

Source code in cdd/json_schema/utils/parse_utils.py
def json_schema_property_to_param(param, required):
    """
    Convert a JSON schema property to a param

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

    :param required: Names of all required parameters
    :type required: ```FrozenSet[str]```

    :return: Name, dict with keys: 'typ', 'doc', 'default'
    :rtype: ```tuple[str, dict]```
    """
    name, _param = param
    del param
    if name.endswith("kwargs"):
        _param["typ"] = "Optional[dict]"
    # elif "enum" in _param:
    #     _param["typ"] = "Literal{}".format(_param.pop("enum"))
    #     del _param["type"]
    if "description" in _param:
        _param["doc"] = _param.pop("description")

    if _param.get("type"):
        _param["typ"] = json_type2typ[_param.pop("type")]

    if _param.get("pattern"):
        maybe_enum = _param["pattern"].split("|")
        if all(filter(str.isalpha, maybe_enum)):
            _param["typ"] = "Literal[{}]".format(
                ", ".join(map("'{}'".format, maybe_enum))
            )
            del _param["pattern"]

    def transform_ref_fk_set(ref, foreign_key):
        """
        Transform $ref to upper camel case and add to the foreign key

        :param ref: JSON ref
        :type ref: ```str```

        :param foreign_key: Foreign key structure (pass by reference)
        :type foreign_key: ```dict```

        :return: $ref without the namespace and in upper camel case
        :rtype: ```str```
        """
        entity = namespaced_pascal_to_upper_camelcase(
            ref.rpartition("/")[2].replace(".", "__")
        )
        foreign_key["fk"] = entity
        return entity

    fk = {"fk": None}
    if "anyOf" in _param:
        _param["typ"] = list(
            map(
                lambda typ: (
                    (
                        transform_ref_fk_set(typ["$ref"], fk)
                        if "$ref" in typ
                        else typ["type"]
                    )
                    if isinstance(typ, dict)
                    else typ
                ),
                _param.pop("anyOf"),
            )
        )

        if len(_param["typ"]) > 1 and "string" in _param["typ"]:
            del _param["typ"][_param["typ"].index("string")]
        _param["typ"] = (
            _param["typ"][0]
            if len(_param["typ"]) == 1
            else "Union[{}]".format(",".join(_param["typ"]))
        )
    elif "$ref" in _param:
        _param["typ"] = transform_ref_fk_set(_param.pop("$ref"), fk)

    if fk["fk"] is not None:
        fk_val = fk.pop("fk")
        fk_prefix = fk_val if fk_val.startswith("[FK(") else "[FK({})]".format(fk_val)
        _param["doc"] = (
            "{} {}".format(fk_prefix, _param["doc"]) if _param.get("doc") else fk_prefix
        )

    if (
        name not in required
        and _param.get("typ")
        and "Optional[" not in _param["typ"]
        # Could also parse out a `Union` for `None`
        or _param.pop("nullable", False)
    ):
        _param["typ"] = "Optional[{}]".format(_param["typ"])
    if _param.get("default", False) in none_types:
        _param["default"] = cdd.shared.ast_utils.NoneStr

    return name, _param

cdd.json_schema.utils.shared_utils

cdd.json_schema.utils.shared_utils

Shared utility functions for JSON schema

Parameters:

Name Type Description Default

cdd.pydantic

cdd.pydantic

pydantic parser and emitter utility module

Parameters:

Name Type Description Default

cdd.pydantic.emit

cdd.pydantic.emit

Pydantic class emitter

https://pydantic-docs.helpmanual.io/usage/schema/

Parameters:

Name Type Description Default

cdd.pydantic.parse

cdd.pydantic.parse

Pydantic class parser

https://pydantic-docs.helpmanual.io/usage/schema/

Parameters:

Name Type Description Default

cdd.routes

cdd.routes

Routes for parsing/emitting. Currently, Bottle, and aimed for OpenAPI.

Parameters:

Name Type Description Default

cdd.routes.emit

cdd.routes.emit

Module of route emitters

Parameters:

Name Type Description Default

cdd.routes.emit.bottle

cdd.routes.emit.bottle

Emit constant strings with interpolated values for route generation

Parameters:

Name Type Description Default

create

create(app, name, route, variant=2)

Create the create route

Parameters:

Name Type Description Default
app str

Variable name (Bottle App)

required
name str

Name of entity

required
route str

The path of the resource

required
variant int

Number of variant

required

Returns:

Name Type Description
return_type str

Create route variant with interpolated values

Source code in cdd/routes/emit/bottle.py
def create(app, name, route, variant=2):
    """
    Create the `create` route

    :param app: Variable name (Bottle App)
    :type app: ```str```

    :param name: Name of entity
    :type name: ```str```

    :param route: The path of the resource
    :type route: ```str```

    :param variant: Number of variant
    :type variant: ```int```

    :return: Create route variant with interpolated values
    :rtype: ```str```
    """
    return create_route_variants[variant].format(app=app, name=name, route=route)

create_util

create_util(name, route, variant=1)

Create utility function that the create emitter above uses

Parameters:

Name Type Description Default
name str

Name of entity

required
route str

The path of the resource

required
variant int

Number of variant

required

Returns:

Name Type Description
return_type str

Create route variant with interpolated values

Source code in cdd/routes/emit/bottle.py
def create_util(name, route, variant=1):
    """
    Create utility function that the `create` emitter above uses

    :param name: Name of entity
    :type name: ```str```

    :param route: The path of the resource
    :type route: ```str```

    :param variant: Number of variant
    :type variant: ```int```

    :return: Create route variant with interpolated values
    :rtype: ```str```
    """
    return create_helper_variants[variant].format(name=name, route=route)

destroy

destroy(app, name, route, primary_key, variant=0)

Create the destroy route

Parameters:

Name Type Description Default
app str

Variable name (Bottle App)

required
name str

Name of entity

required
route str

The path of the resource

required
primary_key Any

The id

required
variant int

Number of variant

required

Returns:

Name Type Description
return_type str

Create route variant with interpolated values

Source code in cdd/routes/emit/bottle.py
def destroy(app, name, route, primary_key, variant=0):
    """
    Create the `destroy` route

    :param app: Variable name (Bottle App)
    :type app: ```str```

    :param name: Name of entity
    :type name: ```str```

    :param route: The path of the resource
    :type route: ```str```

    :param primary_key: The id
    :type primary_key: ```Any```

    :param variant: Number of variant
    :type variant: ```int```

    :return: Create route variant with interpolated values
    :rtype: ```str```
    """
    return delete_route_variants[variant].format(
        app=app, name=name, route=route, id=primary_key
    )

read

read(app, name, route, primary_key, variant=0)

Create the read route

Parameters:

Name Type Description Default
app str

Variable name (Bottle App)

required
name str

Name of entity

required
route str

The path of the resource

required
primary_key Any

The id

required
variant int

Number of variant

required

Returns:

Name Type Description
return_type str

Create route variant with interpolated values

Source code in cdd/routes/emit/bottle.py
def read(app, name, route, primary_key, variant=0):
    """
    Create the `read` route

    :param app: Variable name (Bottle App)
    :type app: ```str```

    :param name: Name of entity
    :type name: ```str```

    :param route: The path of the resource
    :type route: ```str```

    :param primary_key: The id
    :type primary_key: ```Any```

    :param variant: Number of variant
    :type variant: ```int```

    :return: Create route variant with interpolated values
    :rtype: ```str```
    """
    return read_route_variants[variant].format(
        app=app, name=name, route=route, id=primary_key
    )

cdd.routes.emit.bottle_constants_utils

cdd.routes.emit.bottle_constants_utils

Constant strings and tuples of strings which are to be interpolated in emit.py

Parameters:

Name Type Description Default

cdd.routes.parse

cdd.routes.parse

Module of route parsers

Parameters:

Name Type Description Default

cdd.routes.parse.bottle

cdd.routes.parse.bottle

Parsers for routes

Parameters:

Name Type Description Default

bottle

bottle(function_def)

Parse bottle API

Parameters:

Name Type Description Default
function_def Union[FunctionDef, FunctionType]

Function definition of a bottle route, like @api.get("/api") def root(): return "/"

required

Returns:

Name Type Description
return_type dict

OpenAPI representation of the given route

Source code in cdd/routes/parse/bottle.py
def bottle(function_def):
    """
    Parse bottle API

    :param function_def: Function definition of a bottle route, like `@api.get("/api") def root(): return "/"`
    :type function_def: ```Union[FunctionDef, FunctionType]```

    :return: OpenAPI representation of the given route
    :rtype: ```dict```
    """
    if isinstance(function_def, FunctionType):
        # Dynamic function, i.e., this isn't source code; and is in your memory
        function_def: FunctionDef = cast(
            FunctionDef, ast.parse(getsource(function_def)).body[0]
        )

    assert isinstance(
        function_def, FunctionDef
    ), "Expected `FunctionDef` got `{type_name}`".format(
        type_name=type(function_def).__name__
    )
    app_decorator = next(
        filter(
            lambda call: call.func.attr in methods,
            function_def.decorator_list,
        )
    )
    route: str = get_value(app_decorator.args[0])
    name: str = app_decorator.func.value.id
    method: methods_literal_type = app_decorator.func.attr

    route_dict = {"route": route, "name": name, "method": method}
    doc_str: Optional[str] = ast.get_docstring(function_def, clean=True)
    if doc_str is not None:
        ir: IntermediateRepr = parse_docstring(doc_str)
        yml_start_str, yml_end_str = "```yml", "```"
        yml_start: int = ir["doc"].find(yml_start_str)
        # if yml_start < 0:
        #    return route_dict
        openapi_str: str = ir["doc"][
            yml_start
            + len(yml_start_str) : ir["doc"].rfind(yml_end_str)
            - len(yml_end_str)
            + 2
        ]
        return cdd.compound.openapi.parse.openapi(
            openapi_str, route_dict, ir["doc"][:yml_start].rstrip()
        )

cdd.routes.parse.bottle_utils

cdd.routes.parse.bottle_utils

Parser utils for routes

Parameters:

Name Type Description Default

get_route_meta

get_route_meta(mod)

Get the (func_name, app_name, route_path, http_method)s

Parameters:

Name Type Description Default
mod Module

Parsed AST containing routes

required

Returns:

Name Type Description
return_type Iterator[tuple[str, str, str, str]]

Iterator of tuples of (func_name, app_name, route_path, http_method)

Source code in cdd/routes/parse/bottle_utils.py
def get_route_meta(mod):
    """
    Get the (func_name, app_name, route_path, http_method)s

    :param mod: Parsed AST containing routes
    :type mod: ```Module```

    :return: Iterator of tuples of (func_name, app_name, route_path, http_method)
    :rtype: ```Iterator[tuple[str, str, str, str]]```
    """
    return map(
        lambda func: (
            func.name,
            *next(
                map(
                    lambda call: (
                        call.func.value.id,
                        get_value(call.args[0]),
                        call.func.attr,
                    ),
                    filter(
                        lambda call: call.args and call.func.attr in methods,
                        filter(rpartial(isinstance, Call), func.decorator_list),
                    ),
                )
            ),
        ),
        filter(rpartial(isinstance, FunctionDef), mod.body),
    )

cdd.routes.parse.fastapi

cdd.routes.parse.fastapi

FastAPI route parser

Parameters:

Name Type Description Default

fastapi

fastapi(fastapi_route)

Parse a single FastAPI route

Parameters:

Name Type Description Default
fastapi_route AsyncFunctionDef

A single FastAPI route

required

Returns:

Name Type Description
return_type tuple[str, dict]

Pair of (str, dict) consisting of API path to a dictionary of form { Literal["post","get","put","patch"]: { "requestBody": { "$ref": str, "required": boolean }, "responses": { number: { "content": {string: {"schema": {"$ref": string}, "description": string} } } }, "summary": string } }

Source code in cdd/routes/parse/fastapi.py
def fastapi(fastapi_route):
    """
    Parse a single FastAPI route

    :param fastapi_route: A single FastAPI route
    :type fastapi_route: ```AsyncFunctionDef```

    :return: Pair of (str, dict) consisting of API path to a dictionary of form
        {  Literal["post","get","put","patch"]: {
             "requestBody": { "$ref": str, "required": boolean },
             "responses": { number: { "content": {string: {"schema": {"$ref": string},
                                      "description": string} } } },
             "summary": string
           }
        }
    :rtype: ```tuple[str, dict]```
    """
    method: str = fastapi_route.decorator_list[0].func.attr
    route = get_value(fastapi_route.decorator_list[0].args[0])
    return route, {
        method: {
            "responses": parse_fastapi_responses(
                next(
                    filter(
                        lambda keyword: keyword.arg == "responses",
                        fastapi_route.decorator_list[0].keywords,
                    )
                )
            )
        }
    }

cdd.routes.parse.fastapi_utils

cdd.routes.parse.fastapi_utils

FastAPI utils

Parameters:

Name Type Description Default

model_handler

model_handler(key, model_name, location, mime_type)

Create fully-qualified model name from unqualified name

Parameters:

Name Type Description Default
key str

Key name

required
model_name Union[Literal["ref"], str]

Not fully-qualified model name or a {"$ref": string} dict

required
location str

Full-qualified parent path

required
mime_type str

MIME type

required

Returns:

Name Type Description
return_type tuple[Union[str,"content"], dict]

Tuple["content", JSON ref to model name, of form {"$ref": string}]

Source code in cdd/routes/parse/fastapi_utils.py
def model_handler(key, model_name, location, mime_type):
    """
    Create fully-qualified model name from unqualified name

    :param key: Key name
    :type key: ```str```

    :param model_name: Not fully-qualified model name or a `{"$ref": string}` dict
    :type model_name: ```str|dict```

    :param location: Full-qualified parent path
    :type location: ```str```

    :param mime_type: MIME type
    :type mime_type: ```str```

    :return: Tuple["content", JSON ref to model name, of form `{"$ref": string}`]
    :rtype: ```tuple[Union[str,"content"], dict]```
    """
    return (
        (key, model_name)
        if isinstance(model_name, dict)
        else (
            "content",
            {mime_type: {"schema": {"$ref": "{}{}".format(location, model_name)}}},
        )
    )

parse_fastapi_responses

parse_fastapi_responses(responses)

Parse FastAPI "responses" key

Parameters:

Name Type Description Default
responses Dict

responses keyword value from FastAPI decorator on route

required

Returns:

Name Type Description
return_type dict

Transformed FastAPI "responses"

Source code in cdd/routes/parse/fastapi_utils.py
def parse_fastapi_responses(responses):
    """
    Parse FastAPI "responses" key

    :param responses: `responses` keyword value from FastAPI decorator on route
    :type responses: ```Dict```

    :return: Transformed FastAPI "responses"
    :rtype: ```dict```
    """

    return {
        key: dict(
            (
                (
                    lambda _v: (
                        (parse_handlers[k](k, _v)) if k in parse_handlers else (k, _v)
                    )
                )(get_value(v))
            )
            for k, v in Dict_to_dict(val).items()
        )
        for key, val in Dict_to_dict(responses.value).items()
    }

cdd.shared

cdd.shared

cdd-wide shared utilities module

Parameters:

Name Type Description Default

cdd.shared.ast_cst_utils

cdd.shared.ast_cst_utils

Utils for working with AST (builtin) and cdd's CST

Parameters:

Name Type Description Default

Delta

Bases: Enum

Maybe Enum for what every maybe_ function in ast_cst_utils can return

Parameters:

Name Type Description Default

debug_doctrans

debug_doctrans(changed, affector, name, typ)

Print debug statement if changed is not nop

Parameters:

Name Type Description Default
changed Delta

Delta value indicating what changed (if anything)

required
affector str

What is being changed

required
name str

Name of what is being changed

required
typ str

AST type name of what is being changed

required
Source code in cdd/shared/ast_cst_utils.py
def debug_doctrans(changed, affector, name, typ):
    """
    Print debug statement if changed is not nop

    :param changed: Delta value indicating what changed (if anything)
    :type changed: ```Delta```

    :param affector: What is being changed
    :type affector: ```str```

    :param name: Name of what is being changed
    :type name: ```str```

    :param typ: AST type name of what is being changed
    :type typ: ```str```
    """
    if changed is not Delta.nop:
        print(
            "{changed!s}".format(changed=changed).ljust(20),
            "{affector}\t{typ}\t`{name}`".format(affector=affector, typ=typ, name=name),
            sep="",
        )

find_cst_at_ast

find_cst_at_ast(cst_list, node)

Find (first) CST node matching AST node

(uses _location from annotate_ancestry)

Parameters:

Name Type Description Default
cst_list list[NamedTuple]

List of namedtuples with at least ("line_no_start", "line_no_end", "value") attributes

required
node AST

AST node

required

Returns:

Name Type Description
return_type tuple[Optional[int], Optional[NamedTuple]]`

Matching idx and element from cst_list if found else (None, None)

Source code in cdd/shared/ast_cst_utils.py
def find_cst_at_ast(cst_list, node):
    """
    Find (first) CST node matching AST node

    (uses `_location` from `annotate_ancestry`)

    :param cst_list: List of `namedtuple`s with at least ("line_no_start", "line_no_end", "value") attributes
    :type cst_list: ```list[NamedTuple]```

    :param node: AST node
    :type node: ```AST```

    :return: Matching idx and element from cst_list if found else (None, None)
    :rtype: ```tuple[Optional[int], Optional[NamedTuple]]````
    """
    cst_node_found, cst_node_no = None, None
    node_type = type(node).__name__
    cst_type = ast2cst.get(node_type, type(None)).__name__
    if cst_type == "NoneType":
        print("`{node_type}` not implemented".format(node_type=node_type), file=stderr)
        return None, None
    for cst_node_no, cst_node in enumerate(cst_list):
        if (
            cst_node.line_no_start <= node.lineno <= cst_node.line_no_end
            # Extra precautions to ensure the wrong new_node is never replaced:
            and type(cst_node).__name__ == cst_type  # `isinstance` doesn't work
            and getattr(cst_node, "name", None) == getattr(node, "name", None)
        ):
            cst_node_found = cst_node
            break
    return cst_node_no, cst_node_found

maybe_replace_doc_str_in_function_or_class

maybe_replace_doc_str_in_function_or_class(node, cst_idx, cst_list)

Maybe replace the doc_str of a function or class

Parameters:

Name Type Description Default
node Union[ClassDef, AsyncFunctionDef, FunctionDef]

AST node

required
cst_idx int

Index of start of function/class in cst_list

required
cst_list list[NamedTuple]

List of namedtuples with at least ("line_no_start", "line_no_end", "value") attributes

required

Returns:

Name Type Description
return_type Delta

Delta value indicating what changed (if anything)

Source code in cdd/shared/ast_cst_utils.py
def maybe_replace_doc_str_in_function_or_class(node, cst_idx, cst_list):
    """
    Maybe replace the doc_str of a function or class

    :param node: AST node
    :type node: ```Union[ClassDef, AsyncFunctionDef, FunctionDef]```

    :param cst_idx: Index of start of function/class in cst_list
    :type cst_idx: ```int```

    :param cst_list: List of `namedtuple`s with at least ("line_no_start", "line_no_end", "value") attributes
    :type cst_list: ```list[NamedTuple]```

    :return: Delta value indicating what changed (if anything)
    :rtype: ```Delta```
    """
    new_doc_str: str = get_doc_str(node) or ""
    cur_node_after_func = (
        cst_list[cst_idx + 1]
        if cst_idx + 1 < len(cst_list)
        else UnchangingLine(0, 0, "")
    )
    existing_doc_str: bool = (
        isinstance(cur_node_after_func, TripleQuoted) and cur_node_after_func.is_docstr
    )
    changed: Delta = Delta.nop

    def formatted_doc_str(doc_str, is_double_q=True):
        """
        Correctly indent, pre- and post-space the doc_str

        :param doc_str: Input doc string
        :type doc_str: ```str```

        :param is_double_q: Whether the doc_str should be double-quoted
        :type is_double_q: ```bool```

        :return: Correctly formatted `doc_str`
        :rtype: ```str```
        """
        str_after_func_no_nl = cur_node_after_func.value.lstrip("\n")
        indent_after_func_no_nl: int = count_iter_items(
            takewhile(str.isspace, str_after_func_no_nl)
        )
        space: str = str_after_func_no_nl[:indent_after_func_no_nl]
        return TripleQuoted(
            is_double_q=is_double_q,
            is_docstr=True,
            value='\n{space}"""{replacement_doc_str}\n{space}"""'.format(
                space=space,
                replacement_doc_str="\n".join(
                    map(
                        lambda line: "{space}{line}".format(
                            space=str_after_func_no_nl[
                                : indent_after_func_no_nl - len(tab)
                            ],
                            line=line,
                        ),
                        doc_str.split("\n"),
                    )
                ).rstrip(),
            ),
            line_no_start=cur_node_after_func.line_no_start,
            line_no_end=cur_node_after_func.line_no_end,
        )

    if new_doc_str and not existing_doc_str:
        cst_list.insert(
            cst_idx + 1,
            formatted_doc_str(new_doc_str),
        )
        changed = Delta.added
    elif not new_doc_str and existing_doc_str:
        del cst_list[cst_idx + 1]
        changed = Delta.removed
    # elif not new_doc_str and not existing_doc_str: changed = Delta.nop
    elif new_doc_str and existing_doc_str:
        cur_doc_str_only = cur_node_after_func.value.strip()[3:-3]
        if ne(*map(omit_whitespace, (cur_doc_str_only, new_doc_str))):
            pre, _, post = cur_node_after_func.value.partition(cur_doc_str_only)
            cst_list[cst_idx + 1] = formatted_doc_str(
                new_doc_str, is_double_q=cst_list[cst_idx + 1].is_double_q
            )
            changed = Delta.replaced
    if changed is not Delta.nop:
        debug_doctrans(changed, "docstr", node.name, type(node).__name__)
        # Subsequent `line_no` `start,end` lines are invalidated. It's necessary to link the CST and AST together.

    return changed

maybe_replace_function_args

maybe_replace_function_args(new_node, cur_ast_node, cst_idx, cst_list)

Maybe replace the doc_str of a function or class

Parameters:

Name Type Description Default
new_node Union[AsyncFunctionDef, FunctionDef]

AST function node

required
cur_ast_node AST

AST function node of CST (with fake body)

required
cst_idx int

Index of start of function/class in cst_list

required
cst_list list[NamedTuple]

List of namedtuples with at least ("line_no_start", "line_no_end", "value") attributes

required

Returns:

Name Type Description
return_type Delta

Delta value indicating what changed (if anything)

Source code in cdd/shared/ast_cst_utils.py
def maybe_replace_function_args(new_node, cur_ast_node, cst_idx, cst_list):
    """
    Maybe replace the doc_str of a function or class

    :param new_node: AST function node
    :type new_node: ```Union[AsyncFunctionDef, FunctionDef]```

    :param cur_ast_node: AST function node of CST (with fake body)
    :type cur_ast_node: ```AST```

    :param cst_idx: Index of start of function/class in cst_list
    :type cst_idx: ```int```

    :param cst_list: List of `namedtuple`s with at least ("line_no_start", "line_no_end", "value") attributes
    :type cst_list: ```list[NamedTuple]```

    :return: Delta value indicating what changed (if anything)
    :rtype: ```Delta```
    """
    new_node = deepcopy(new_node)
    new_node.body = cur_ast_node.body
    changed: Delta = Delta.nop
    if not cmp_ast(cur_ast_node.args, new_node.args):
        new_args, cur_args = map(attrgetter("args.args"), (new_node, cur_ast_node))

        for i in range(len(cur_args)):
            if cur_args[i].annotation != new_args[i].annotation:
                # Approximation, obviously you could have intermixed annotation and to-be (un)annotated
                if cur_args[i].annotation is None:
                    changed: Delta = Delta.added
                elif new_args[i].annotation is None:
                    changed: Delta = Delta.removed
                else:
                    changed: Delta = Delta.replaced
                break

        def_len: int = len("def ")
        function_name_starts_at: int = (
            def_len
            if cst_list[cst_idx].value.startswith("def ")
            else (lambda i: cst_list[cst_idx].value.find(")def ") if i == -1 else i)(
                cst_list[cst_idx].value.find(" def ")
            )
            + def_len
            + 1
        )
        arg_start_idx: int = cst_list[cst_idx].value.find("(", function_name_starts_at)
        func_end: int = cst_list[cst_idx].value.rfind(":")
        return_type: Optional[int] = cst_list[cst_idx].value.rfind("->", None, func_end)
        if return_type > -1:
            last_col = func_end
            func_end = return_type
            return_type = cst_list[cst_idx].value[return_type + len("->") : last_col]
        else:
            return_type = None
        func_end = cst_list[cst_idx].value.rfind(")", None, func_end) + 1

        # returns="" if return_type is None else return_type

        cst_list[cst_idx] = FunctionDefinitionStart(
            line_no_start=cst_list[cst_idx].line_no_start,
            line_no_end=cst_list[cst_idx].line_no_end,
            name=cst_list[cst_idx].name,
            # TODO: Handle comments in the middle of args, and match whitespace, and maybe even limit line length
            value="{start}{args}{end}".format(
                start=cst_list[cst_idx].value[: arg_start_idx + 1],
                end=cst_list[cst_idx].value[func_end - 1 :],
                args=", ".join(
                    "{arg_name}{annotation}".format(
                        annotation=(
                            ""
                            if arg.annotation is None
                            else ": {annotation_unparsed}".format(
                                annotation_unparsed=to_code(arg.annotation).rstrip("\n")
                            )
                        ),
                        arg_name=arg.arg,
                    )
                    for arg in new_args
                ),
            ),
        )

    if changed is not Delta.nop:
        debug_doctrans(changed, "args", new_node.name, type(new_node).__name__)

    return changed

maybe_replace_function_return_type

maybe_replace_function_return_type(new_node, cur_ast_node, cst_idx, cst_list)

Maybe replace the function's return type

Parameters:

Name Type Description Default
new_node Union[AsyncFunctionDef, FunctionDef]

AST function node

required
cur_ast_node AST

AST function node of CST (with fake body)

required
cst_idx int

Index of start of function/class in cst_list

required
cst_list list[NamedTuple]

List of namedtuples with at least ("line_no_start", "line_no_end", "value") attributes

required

Returns:

Name Type Description
return_type Delta

Delta value indicating what changed (if anything)

Source code in cdd/shared/ast_cst_utils.py
def maybe_replace_function_return_type(new_node, cur_ast_node, cst_idx, cst_list):
    """
    Maybe replace the function's return type

    :param new_node: AST function node
    :type new_node: ```Union[AsyncFunctionDef, FunctionDef]```

    :param cur_ast_node: AST function node of CST (with fake body)
    :type cur_ast_node: ```AST```

    :param cst_idx: Index of start of function/class in cst_list
    :type cst_idx: ```int```

    :param cst_list: List of `namedtuple`s with at least ("line_no_start", "line_no_end", "value") attributes
    :type cst_list: ```list[NamedTuple]```

    :return: Delta value indicating what changed (if anything)
    :rtype: ```Delta```
    """
    new_node = deepcopy(new_node)
    new_node.body = cur_ast_node.body
    value: Optional[str] = None

    def remove_return_typ(statement):
        """
        Remove the return typ

        :param statement: The statement verbatim
        :type statement: ```str```

        :return: The new function prototype
        :rtype: ```str```
        """
        return "{type_less}:".format(
            type_less=statement[: statement.rfind("->")].rstrip()
        )

    def add_return_typ(statement):
        """
        Add the return typ

        :param statement: The statement verbatim
        :type statement: ```str```

        :return: The new function prototype
        :rtype: ```str```
        """
        pre, col, post = statement.rpartition(":")
        return "{pre} -> {return_typ}{col}{post}".format(
            pre=pre,
            return_typ=to_code(new_node.returns).rstrip("\n"),
            col=col,
            post=post,
        )

    if cmp_ast(cur_ast_node.returns, new_node.returns):
        changed: Delta = Delta.nop
    elif cur_ast_node.returns and new_node.returns:
        changed: Delta = Delta.replaced
        value = add_return_typ(remove_return_typ(cst_list[cst_idx].value))
    elif cur_ast_node.returns and not new_node.returns:
        changed: Delta = Delta.removed
        value = remove_return_typ(cst_list[cst_idx].value)
    else:  # not cur_ast_node.returns and new_node.returns:
        changed: Delta = Delta.added
        value = add_return_typ(cst_list[cst_idx].value)
    if value is not None:
        cst_list[cst_idx] = FunctionDefinitionStart(
            line_no_start=cst_list[cst_idx].line_no_start,
            line_no_end=cst_list[cst_idx].line_no_end,
            name=cst_list[cst_idx].name,
            value=value,
        )

    if changed is not Delta.nop:
        debug_doctrans(changed, "return_type", new_node.name, type(new_node).__name__)

    return changed

cdd.shared.ast_utils

cdd.shared.ast_utils

ast_utils, a bunch of helpers for converting input into ast.* input_str

Parameters:

Name Type Description Default

RewriteAtQuery

RewriteAtQuery(search, replacement_node)

Bases: NodeTransformer

Replace the node at query with given node

Parameters:

Name Type Description Default
search

Search query, e.g., ['node_name', 'function_name', 'arg_name']

required
replacement_node

Node to replace this search

required
replaced

Whether a node has been replaced (only replaces first occurrence)

required

Parameters:

Name Type Description Default
search list[str]

Search query, e.g., ['node_name', 'function_name', 'arg_name']

required
replacement_node AST

Node to replace this search

required
Source code in cdd/shared/ast_utils.py
def __init__(self, search, replacement_node):
    """
    :param search: Search query, e.g., ['node_name', 'function_name', 'arg_name']
    :type search: ```list[str]```

    :param replacement_node: Node to replace this search
    :type replacement_node: ```AST```
    """
    self.search = search
    self.replacement_node = replacement_node
    self.replaced = False

generic_visit

generic_visit(node)

visits the AST, if it's the right one, replace it

Parameters:

Name Type Description Default
node AST

The AST node

required

Returns:

Name Type Description
return_type AST

Potentially changed AST node

Source code in cdd/shared/ast_utils.py
def generic_visit(self, node):
    """
    visits the `AST`, if it's the right one, replace it

    :param node: The AST node
    :type node: ```AST```

    :return: Potentially changed AST node
    :rtype: ```AST```
    """
    if (
        not self.replaced
        and hasattr(node, "_location")
        and node._location == self.search
    ):
        self.replaced = True
        return self.replacement_node
    else:
        return NodeTransformer.generic_visit(self, node)

visit_FunctionDef

visit_FunctionDef(node)

visits the FunctionDef, if it's the right one, replace it

Parameters:

Name Type Description Default
node FunctionDef

FunctionDef

required

Returns:

Name Type Description
return_type FunctionDef

Potentially changed FunctionDef

Source code in cdd/shared/ast_utils.py
def visit_FunctionDef(self, node):
    """
    visits the `FunctionDef`, if it's the right one, replace it

    :param node: FunctionDef
    :type node: ```FunctionDef```

    :return: Potentially changed FunctionDef
    :rtype: ```FunctionDef```
    """

    if (
        not self.replaced
        and hasattr(node, "_location")
        and node._location == self.search[:-1]
    ):
        if isinstance(self.replacement_node, (AnnAssign, Assign)):
            # Set default
            if isinstance(self.replacement_node, AnnAssign):
                idx = next(
                    (
                        _arg._idx
                        for _arg in node.args.args
                        if _arg.arg == self.replacement_node.target.id
                        and hasattr(_arg, "_idx")
                    ),
                    None,
                )
            else:
                idx = next(
                    filter(
                        None,
                        (
                            _arg._idx if _arg.arg == target.id else None
                            for target in self.replacement_node.targets
                            for _arg in node.args.args
                            if hasattr(_arg, "_idx")
                        ),
                    ),
                    None,
                )
                self.replacement_node = set_arg(
                    arg=self.replacement_node.targets[0].id,
                    annotation=self.replacement_node.value,
                )

            if idx is not None and len(node.args.defaults) > idx:
                new_default = get_value(self.replacement_node)
                if new_default not in none_types:
                    node.args.defaults[idx] = new_default

            self.replacement_node = emit_arg(self.replacement_node)
        assert isinstance(
            self.replacement_node, ast.arg
        ), "Expected `ast.arg` got `{type_name}`".format(
            type_name=type(self.replacement_node).__name__
        )

        for arg_attr in "args", "kwonlyargs":
            arg_l = getattr(node.args, arg_attr)
            for idx in range(len(arg_l)):
                if (
                    hasattr(arg_l[idx], "_location")
                    and arg_l[idx]._location == self.search
                ):
                    arg_l[idx] = emit_arg(self.replacement_node)
                    self.replaced = True
                    break

    return node

Undefined

Null class

Parameters:

Name Type Description Default

Dict_to_dict

Dict_to_dict(d)

Create a dict from a Dict

Parameters:

Name Type Description Default
d Dict

ast.Dict

required

Returns:

Name Type Description
return_type dict

Python dictionary

Source code in cdd/shared/ast_utils.py
def Dict_to_dict(d):
    """
    Create a `dict` from a `Dict`

    :param d: ast.Dict
    :type d: ```Dict```

    :return: Python dictionary
    :rtype: ```dict```
    """
    return dict(zip(map(get_value, d.keys), map(get_value, d.values)))

annotate_ancestry

annotate_ancestry(node, filename=None)

Look to your roots. Find the child; find the parent. Sets _location and file attributes to every child node.

Parameters:

Name Type Description Default
node AST

AST node. Will be annotated in-place.

required
filename Optional[str]

Where the node was originally defined. Sets the __file__ attribute to this.

required

Returns:

Name Type Description
return_type AST

Annotated AST node; also node arg will be annotated in-place.

Source code in cdd/shared/ast_utils.py
def annotate_ancestry(node, filename=None):
    """
    Look to your roots. Find the child; find the parent.
    Sets _location and __file__ attributes to every child node.

    :param node: AST node. Will be annotated in-place.
    :type node: ```AST```

    :param filename: Where the node was originally defined. Sets the `__file__` attribute to this.
    :type filename: ```Optional[str]```

    :return: Annotated AST node; also `node` arg will be annotated in-place.
    :rtype: ```AST```
    """
    # print("annotating", getattr(node, "name", None))
    node._location = [node.name] if hasattr(node, "name") else []
    if filename not in (None, "<unknown>") and isinstance(
        node, (AnnAssign, Assign, AsyncFunctionDef, ClassDef, FunctionDef, Module)
    ):
        setattr(node, "__file__", filename)
    parent_location = []
    for _node in walk(node):
        name = [_node.name] if hasattr(_node, "name") else []
        if filename not in (None, "<unknown>") and isinstance(
            _node, (AnnAssign, Assign, AsyncFunctionDef, ClassDef, FunctionDef, Module)
        ):
            setattr(_node, "__file__", filename)
        for child_node in iter_child_nodes(_node):
            if hasattr(child_node, "name") and not isinstance(child_node, alias):
                child_node._location = name + [child_node.name]
                parent_location = child_node._location
            elif isinstance(child_node, (Constant, Str)):
                child_node._location = parent_location + [get_value(child_node)]
            elif isinstance(child_node, Assign) and all(
                map(
                    rpartial(isinstance, Name),
                    child_node.targets,
                )
            ):
                for target in child_node.targets:
                    child_node._location = name + [target.id]
            elif isinstance(child_node, AnnAssign) and isinstance(
                child_node.target, Name
            ):
                child_node._location = name + [child_node.target.id]

            if isinstance(child_node, (AsyncFunctionDef, FunctionDef)):

                def set_index_and_location(idx_arg):
                    """
                    :param idx_arg: Index and Any; probably out of `enumerate`
                    :type idx_arg: ```tuple[int, Any]```

                    :return: Second element, with _idx set with value of first
                    :rtype: ```Any```
                    """
                    idx_arg[1]._idx = idx_arg[0]
                    idx_arg[1]._location = child_node._location + [idx_arg[1].arg]
                    return idx_arg[1]

                child_node.args.args = list(
                    map(
                        set_index_and_location,
                        enumerate(
                            child_node.args.args,
                            (
                                -1
                                if len(child_node.args.args) > 0
                                and child_node.args.args[0].arg
                                in frozenset(("self", "cls"))
                                else 0
                            ),
                        ),
                    )
                )

                child_node.args.kwonlyargs = list(
                    map(
                        set_index_and_location,
                        enumerate(
                            child_node.args.kwonlyargs,
                            0,
                        ),
                    )
                )
    return node

ast_elts_to_container

ast_elts_to_container(node, container)

Convert AST container to Python container

Parameters:

Name Type Description Default
node AST

AST node with elts attribute

required
container type

Python container

required

Returns:

Name Type Description
return_type instanceof container

Python container

Source code in cdd/shared/ast_utils.py
def ast_elts_to_container(node, container):
    """
    Convert AST container to Python container

    :param node: AST node with elts attribute
    :type node: ```AST```

    :param container: Python container
    :type container: ```type```

    :return: Python container
    :rtype: ```instanceof container```
    """
    assert hasattr(node, "elts")
    return container(map(get_value, node.elts))

ast_type_to_python_type

ast_type_to_python_type(node)

Unparse AST type as Python type

Implementation notes: - this focuses on 'evaluated scalars' that can be represented as JSON - think of this as a get_value alternative

Parameters:

Name Type Description Default
node Union[Num,Bytes,Str,Constant,Dict,Set,Tuple,List]

AST node

required

Returns:

Name Type Description
return_type Union[dict,str,int,float,complex,bytes,list,tuple,set]
Source code in cdd/shared/ast_utils.py
def ast_type_to_python_type(node):
    """
    Unparse AST type as Python type

    Implementation notes:
      - this focuses on 'evaluated scalars' that can be represented as JSON
      - think of this as a `get_value` alternative

    :param node: AST node
    :type node: ```Union[Num,Bytes,Str,Constant,Dict,Set,Tuple,List]```

    :rtype: Union[dict,str,int,float,complex,bytes,list,tuple,set]
    """
    assert isinstance(node, AST), "Expected `AST` got `{type_name}`".format(
        type_name=type(node).__name__
    )
    if isinstance(node, Num):
        return node.n
    elif isinstance(node, (Bytes, Str)):
        return node.s
    elif isinstance(node, Constant):
        return node.value
    elif isinstance(node, Dict):
        return Dict_to_dict(node)
    elif isinstance(node, Set):
        return Set_to_set(node)
    elif isinstance(node, Tuple):
        return Tuple_to_tuple(node)
    elif isinstance(node, List):
        return List_to_list(node)
    else:
        raise NotImplementedError(node)

cmp_ast

cmp_ast(node0, node1)

Compare if two nodes are equal. Verbatim stolen from meta.asttools.

Parameters:

Name Type Description Default
node0 Union[AST, List[AST], Tuple[AST]]

First node

required
node1 Union[AST, List[AST], Tuple[AST]]

Second node

required

Returns:

Name Type Description
return_type bool

Whether they are equal (recursive)

Source code in cdd/shared/ast_utils.py
def cmp_ast(node0, node1):
    """
    Compare if two nodes are equal. Verbatim stolen from `meta.asttools`.

    :param node0: First node
    :type node0: ```Union[AST, List[AST], Tuple[AST]]```

    :param node1: Second node
    :type node1: ```Union[AST, List[AST], Tuple[AST]]```

    :return: Whether they are equal (recursive)
    :rtype: ```bool```
    """

    if type(node0) is not type(node1):
        return False

    if isinstance(node0, (list, tuple)):
        if len(node0) != len(node1):
            return False

        for left, right in zip(node0, node1):
            if not cmp_ast(left, right):
                return False

    elif isinstance(node0, AST):
        for field in node0._fields:
            left = getattr(node0, field, Undefined)
            right = getattr(node1, field, Undefined)

            if not cmp_ast(left, right):
                return False
    else:
        return node0 == node1

    return True

code_quoted

code_quoted(s)

Internally user-provided None and non literal_evalable input is quoted with ```

This function checks if the input is quoted such

Parameters:

Name Type Description Default
s Any

The input

required

Returns:

Name Type Description
return_type bool

Whether the input is code quoted

Source code in cdd/shared/pure_utils.py
def code_quoted(s):
    """
    Internally user-provided `None` and non `literal_eval`able input is quoted with ```

    This function checks if the input is quoted such

    :param s: The input
    :type s: ```Any```

    :return: Whether the input is code quoted
    :rtype: ```bool```
    """
    return (
        isinstance(s, str) and len(s) > 6 and s.startswith("```") and s.endswith("```")
    )

construct_module_with_symbols

construct_module_with_symbols(module, symbols)

Create a module out of the input module that only contains nodes with a name contained in symbols

Parameters:

Name Type Description Default
module Module

Input module

required
symbols FrozenSet[str]

Symbols

required

Returns:

Name Type Description
return_type Module

Module with only members whose .name is in symbols

Source code in cdd/shared/ast_utils.py
def construct_module_with_symbols(module, symbols):
    """
    Create a module out of the input module that only contains nodes
    with a name contained in `symbols`

    :param module: Input module
    :type module: ```Module```

    :param symbols: Symbols
    :type symbols: ```FrozenSet[str]```

    :return: Module with only members whose `.name` is in `symbols`
    :rtype: ```Module```
    """
    return Module(
        body=list(
            filter(lambda node: getattr(node, "name", "") in symbols, module.body)
        ),
        type_ignores=[],
        stmt=None,
    )

deduplicate

deduplicate(it)

Deduplicate an iterable

Parameters:

Name Type Description Default
it Union[Iterable, Generator, List, Tuple, Set, FrozenSet]

An iterable|collection with hashable elements

required

Returns:

Name Type Description
return_type Iterable

Deduplicated iterable of the input

Source code in cdd/shared/ast_utils.py
def deduplicate(it):
    """
    Deduplicate an iterable

    :param it: An iterable|collection with hashable elements
    :type it: ```Union[Iterable, Generator, List, Tuple, Set, FrozenSet]```

    :return: Deduplicated iterable of the input
    :rtype: ```Iterable```
    """
    seen = set()
    return (seen.add(e) or e for e in it if e not in seen)

deduplicate_sorted_imports

deduplicate_sorted_imports(module)

Deduplicate sorted imports. NOTE: for a more extensive solution use isort or ruff.

Parameters:

Name Type Description Default
module Module

Module

required

Returns:

Name Type Description
return_type Module

Module but with duplicate import entries in first import block removed

Source code in cdd/shared/ast_utils.py
def deduplicate_sorted_imports(module):
    """
    Deduplicate sorted imports. NOTE: for a more extensive solution use isort or ruff.

    :param module: Module
    :type module: ```Module```

    :return: Module but with duplicate import entries in first import block removed
    :rtype: ```Module```
    """
    assert isinstance(module, Module), "Expected `Module` got `{}`".format(
        type(module).__name__
    )
    fst_import_idx: Optional[int] = next(
        map(
            itemgetter(0),
            filter(
                lambda idx_node: isinstance(idx_node[1], (ImportFrom, Import)),
                enumerate(module.body),
            ),
        ),
        None,
    )
    if fst_import_idx is None:
        return module
    lst_import_idx: Optional[int] = next(
        iter(
            deque(
                map(
                    itemgetter(0),
                    filter(
                        lambda idx_node: isinstance(idx_node[1], (ImportFrom, Import)),
                        enumerate(module.body, fst_import_idx),
                    ),
                ),
                maxlen=1,
            )
        ),
        None,
    )
    name_seen: MutableSet[str] = set()

    module.body = (
        module.body[:fst_import_idx]
        + list(
            filter(
                attrgetter("names"),
                (
                    # TODO: Infer `level`
                    ImportFrom(
                        module=name,
                        names=list(
                            filter(
                                lambda _alias: (
                                    lambda key: (
                                        False
                                        if key in name_seen
                                        else (name_seen.add(key) or True)
                                    )
                                )(
                                    "<name={!r}, alias.name={!r}, alias.asname={!r}>".format(
                                        name, _alias.name, _alias.asname
                                    )
                                ),
                                sorted(
                                    chain.from_iterable(
                                        map(attrgetter("names"), import_from_nodes)
                                    ),
                                    key=attrgetter("name"),
                                ),
                            )
                        ),
                        level=0,  # import_from_nodes[0].level
                        identifier=None,
                    )
                    for name, import_from_nodes in groupby(
                        module.body[fst_import_idx:lst_import_idx],
                        key=attrgetter("module"),
                    )
                ),
            )
        )
        + module.body[lst_import_idx:]
    )
    name_seen.clear()
    return module

del_ass_where_name

del_ass_where_name(node, name)

Delete all Assign/AnnAssign in node body where id matches name

Parameters:

Name Type Description Default
node Union[Module, ClassDef, FunctionDef, If, Try, While, With, AsyncFor, AsyncFunctionDef, AsyncWith, ExceptHandler, Expression, For, IfExp, Interactive, Lambda ]

AST node with a '.body'

required
name id

Name to match (matches against id field of Name)

required
Source code in cdd/shared/ast_utils.py
def del_ass_where_name(node, name):
    """
    Delete all `Assign`/`AnnAssign` in node body where id matches name

    :param node: AST node with a '.body'
    :type node: ```Union[Module, ClassDef, FunctionDef, If, Try, While, With, AsyncFor, AsyncFunctionDef, AsyncWith,
                         ExceptHandler, Expression, For, IfExp, Interactive, Lambda ]```

    :param name: Name to match (matches against `id` field of `Name`)
    :type name: ```str```
    """
    node.body = list(
        filter(
            None,
            (
                (
                    None
                    if isinstance(_node, Assign)
                    and name
                    in frozenset(
                        map(
                            attrgetter("id"),
                            filter(rpartial(isinstance, Name), _node.targets),
                        )
                    )
                    or isinstance(_node, AnnAssign)
                    and _node.target == name
                    else _node
                )
                for _node in node.body
            ),
        )
    )

emit_ann_assign

emit_ann_assign(node)

Produce an AnnAssign from the input

Parameters:

Name Type Description Default
node AST

AST node

required

Returns:

Name Type Description
return_type AnnAssign

Something which parses to the form of a=5

Source code in cdd/shared/ast_utils.py
def emit_ann_assign(node):
    """
    Produce an `AnnAssign` from the input

    :param node: AST node
    :type node: ```AST```

    :return: Something which parses to the form of `a=5`
    :rtype: ```AnnAssign```
    """
    if isinstance(node, AnnAssign):
        return node
    elif isinstance(node, ast.arg):
        return AnnAssign(
            annotation=node.annotation,
            simple=1,
            target=Name(node.arg, Store(), lineno=None, col_offset=None),
            col_offset=getattr(node, "col_offset", None),
            end_lineno=getattr(node, "end_lineno", None),
            end_col_offset=getattr(node, "end_col_offset", None),
            expr=None,
            expr_target=None,
            expr_annotation=None,
            lineno=None,
            value=node.default if getattr(node, "default", None) is not None else None,
        )
    else:
        raise NotImplementedError(type(node).__name__)

emit_arg

emit_arg(node)

Produce an arg from the input

Parameters:

Name Type Description Default
node AST

AST node

required

Returns:

Name Type Description
return_type ast.arg

Something which parses to the form of a=5

Source code in cdd/shared/ast_utils.py
def emit_arg(node):
    """
    Produce an `arg` from the input

    :param node: AST node
    :type node: ```AST```

    :return: Something which parses to the form of `a=5`
    :rtype: ```ast.arg```
    """
    if isinstance(node, ast.arg):
        return node
    elif isinstance(node, AnnAssign) and isinstance(node.target, Name):
        return set_arg(
            annotation=node.annotation,
            arg=node.target.id,
        )
    elif (
        isinstance(node, Assign)
        and len(node.targets) == 1
        and isinstance(node.targets[0], Name)
    ):
        return set_arg(node.targets[0].id)
    else:
        raise NotImplementedError(type(node).__name__)

find_ast_type

find_ast_type(node, node_name=None, of_type=ClassDef)

Finds first AST node of the given type and possibly name

Parameters:

Name Type Description Default
node AST

Any AST node

required
node_name Optional[str]

Name of AST node. If None, gives first found.

required
of_type AST

Of which type to find

required

Returns:

Name Type Description
return_type AST

Found AST node

Source code in cdd/shared/ast_utils.py
def find_ast_type(node, node_name=None, of_type=ClassDef):
    """
    Finds first AST node of the given type and possibly name

    :param node: Any AST node
    :type node: ```AST```

    :param node_name: Name of AST node. If None, gives first found.
    :type node_name: ```Optional[str]```

    :param of_type: Of which type to find
    :type of_type: ```AST```

    :return: Found AST node
    :rtype: ```AST```
    """
    if isinstance(node, Module):
        it: Optional[Generator[of_type]] = filter(
            rpartial(isinstance, of_type), node.body
        )
        if node_name is not None:
            return next(
                filter(
                    lambda e: hasattr(e, "name") and e.name == node_name,
                    it,
                )
            )
        matching_nodes = tuple(it)  # type: tuple[of_type, ...]
        if len(matching_nodes) > 1:  # We could convert every one I guess?
            raise NotImplementedError()
        elif matching_nodes:
            return matching_nodes[0]
        else:
            raise TypeError(
                "No {type_name!r} in AST".format(type_name=type(of_type).__name__)
            )
    elif isinstance(node, AST):
        assert node_name is None or not hasattr(node, "name") or node.name == node_name
        return node
    else:
        raise NotImplementedError(type(node).__name__)

find_in_ast

find_in_ast(search, node)

Find and return the param from within the value

Parameters:

Name Type Description Default
search list[str]

Location within AST of property. Can be top level like ['a'] for a=5 or E.g., ['A', 'F'] for class A: F, ['f', 'g'] for def f(g): ...

required
node AST

AST node (must have a body)

required

Returns:

Name Type Description
return_type Optional[AST]

AST node that was found, or None if nothing was found

Source code in cdd/shared/ast_utils.py
def find_in_ast(search, node):
    """
    Find and return the param from within the value

    :param search: Location within AST of property.
       Can be top level like `['a']` for `a=5` or E.g., `['A', 'F']` for `class A: F`, `['f', 'g']` for `def f(g): ...`
    :type search: ```list[str]```

    :param node: AST node (must have a `body`)
    :type node: ```AST```

    :return: AST node that was found, or None if nothing was found
    :rtype: ```Optional[AST]```
    """
    if not search or hasattr(node, "_location") and node._location == search:
        return node

    child_node, cursor, current_search = node, node.body, deepcopy(search)
    while len(current_search):
        query = current_search.pop(0)
        if (
            len(current_search) == 0
            and hasattr(child_node, "name")
            and child_node.name == query
        ):
            return child_node

        for child_node in cursor:
            if hasattr(child_node, "_location") and child_node._location == search:
                return child_node

            elif isinstance(child_node, FunctionDef):
                if len(current_search):
                    query = current_search.pop(0)
                _cursor = next(
                    filter(
                        lambda idx_arg: idx_arg[1].arg == query,
                        enumerate(child_node.args.args),
                    ),
                    None,
                )
                if _cursor is not None:
                    if len(child_node.args.defaults) > _cursor[0]:
                        setattr(
                            _cursor[1], "default", child_node.args.defaults[_cursor[0]]
                        )
                    cursor = _cursor[1]
                    if len(current_search) == 0:
                        return cursor
            elif (
                isinstance(child_node, AnnAssign)
                and isinstance(child_node.target, Name)
                and child_node.target.id == query
            ):
                return child_node
            elif hasattr(child_node, "name") and child_node.name == query:
                cursor = child_node.body
                break

func_arg2param

func_arg2param(func_arg, default=None)

Converts a function argument to a param tuple

Parameters:

Name Type Description Default
func_arg ast.arg

Function argument

required
default dict

The default value, if None isn't added to returned dict

required

Returns:

Name Type Description
return_type tuple[str, dict]

Name, dict with keys: 'typ', 'doc', 'default'

Source code in cdd/shared/ast_utils.py
def func_arg2param(func_arg, default=None):
    """
    Converts a function argument to a param tuple

    :param func_arg: Function argument
    :type func_arg: ```ast.arg```

    :param default: The default value, if None isn't added to returned dict
    :type default: ```Optional[Any]```

    :return: Name, dict with keys: 'typ', 'doc', 'default'
    :rtype: ```tuple[str, dict]```
    """
    return func_arg.arg, dict(
        doc=getattr(func_arg, "type_comment", None),
        **dict(
            typ=(
                None
                if func_arg.annotation is None
                else _to_code(func_arg.annotation).rstrip("\n")
            ),
            **({} if default is None else {"default": default}),
        ),
    )

get_ass_where_name

get_ass_where_name(node, name)

Find all Assign/AnnAssign in node body where id matches name

Parameters:

Name Type Description Default
node Union[Module, ClassDef, FunctionDef, If, Try, While, With, AsyncFor, AsyncFunctionDef, AsyncWith, ExceptHandler, Expression, For, IfExp, Interactive, Lambda ]

AST node with a '.body'

required
name id

Name to match (matches against id field of Name)

required

Returns:

Name Type Description
return_type Generator[Union[Assign, AnnAssign]]

Generator of all matches names (.value)

Source code in cdd/shared/ast_utils.py
def get_ass_where_name(node, name):
    """
    Find all `Assign`/`AnnAssign` in node body where id matches name

    :param node: AST node with a '.body'
    :type node: ```Union[Module, ClassDef, FunctionDef, If, Try, While, With, AsyncFor, AsyncFunctionDef, AsyncWith,
                         ExceptHandler, Expression, For, IfExp, Interactive, Lambda ]```

    :param name: Name to match (matches against `id` field of `Name`)
    :type name: ```str```

    :return: Generator of all matches names (.value)
    :rtype: ```Generator[Union[Assign, AnnAssign]]```
    """
    return (
        get_value(_node)
        for _node in node.body
        if isinstance(_node, Assign)
        and name
        in frozenset(
            map(attrgetter("id"), filter(rpartial(isinstance, Name), _node.targets))
        )
        or isinstance(_node, AnnAssign)
        and get_value(_node.target) == name
    )

get_at_root

get_at_root(node, types)

Get the imports from a node

Parameters:

Name Type Description Default
node AST

AST node with .body, probably an ast.Module

required
types tuple[str,...]`

The types to search for (uses in an isinstance check)

required

Returns:

Name Type Description
return_type list[Union[]]

List of imports. Doesn't handle those within a try/except, condition, or not in root scope

Source code in cdd/shared/ast_utils.py
def get_at_root(node, types):
    """
    Get the imports from a node

    :param node: AST node with .body, probably an `ast.Module`
    :type node: ```AST```

    :param types: The types to search for (uses in an `isinstance` check)
    :type types: ```tuple[str,...]````

    :return: List of imports. Doesn't handle those within a try/except, condition, or not in root scope
    :rtype: ```list[Union[]]```
    """
    assert hasattr(node, "body") and isinstance(node.body, (list, tuple))
    return list(filter(rpartial(isinstance, types), node.body))

get_doc_str

get_doc_str(node)

Similar to ast.get_docstring except never cleans and returns None on failure rather than raising

Parameters:

Name Type Description Default
node AST

AST node

required

Returns:

Name Type Description
return_type Optional[str]

Docstring if found else None

Source code in cdd/shared/ast_utils.py
def get_doc_str(node):
    """
    Similar to `ast.get_docstring` except never `clean`s and returns `None` on failure rather than raising

    :param node: AST node
    :type node: ```AST```

    :return: Docstring if found else None
    :rtype: ```Optional[str]```
    """
    if isinstance(node, (ClassDef, FunctionDef)) and isinstance(node.body[0], Expr):
        val = get_value(node.body[0])
        if isinstance(val, (Constant, Str)):
            return get_value(val)

get_function_type

get_function_type(function_def)

Get the type of the function

Parameters:

Name Type Description Default
function_def FunctionDef

AST node for function definition

required

Returns:

Name Type Description
return_type Literal['self', 'cls', 'static']

Type of target, static is static or global method, others just become first arg

Source code in cdd/shared/ast_utils.py
def get_function_type(function_def):
    """
    Get the type of the function

    :param function_def: AST node for function definition
    :type function_def: ```FunctionDef```

    :return: Type of target, static is static or global method, others just become first arg
    :rtype: ```Literal['self', 'cls', 'static']```
    """
    assert isinstance(
        function_def, FunctionDef
    ), "Expected `FunctionDef` got `{type_name}`".format(
        type_name=type(function_def).__name__
    )
    if (
        not hasattr(function_def, "args")
        or function_def.args is None
        or not function_def.args.args
    ):
        return "static"
    elif function_def.args.args[0].arg in frozenset(("self", "cls")):
        return function_def.args.args[0].arg
    return "static"

get_names

get_names(node)

Get name(s) from: - Assign targets - AnnAssign target - Function, AsyncFunction, ClassDef

Parameters:

Name Type Description Default
node Union[Assign, AnnAssign, Function, AsyncFunctionDef, ClassDef]

AST node

required

Returns:

Name Type Description
return_type Generator[str]

All top-level symbols (except those within try/except and if/elif/else blocks)

Source code in cdd/shared/ast_utils.py
def get_names(node):
    """
    Get name(s) from:
    - Assign targets
    - AnnAssign target
    - Function, AsyncFunction, ClassDef

    :param node: AST node
    :type node: ```Union[Assign, AnnAssign, Function, AsyncFunctionDef, ClassDef]```

    :return: All top-level symbols (except those within try/except and if/elif/else blocks)
    :rtype: ```Generator[str]```
    """
    if isinstance(node, Assign) and all(map(rpartial(isinstance, Name), node.targets)):
        return map(attrgetter("id"), node.targets)
    elif isinstance(node, AnnAssign) and isinstance(node.target, Name):
        return iter((node.target.id,))
    elif isinstance(node, (AsyncFunctionDef, FunctionDef, ClassDef)):
        return iter((node.name,))
    return iter(())

get_types

get_types(node)

Parameters:

Name Type Description Default
node AST|str
required

Returns:

Name Type Description
return_type Generator[str]
Source code in cdd/shared/ast_utils.py
def get_types(node):
    """
    :param node:
    :type node: ```AST|str```

    :rtype: ```Generator[str]```
    """
    if node is None:
        return iter(())
    elif isinstance(node, str):
        return iter((node,))
    elif isinstance(node, Name):
        return iter((node.id,))
    elif isinstance(node, Subscript):
        assert isinstance(node.value, Name), type(node.value)
        if isinstance(node.slice, Name):
            return iter((node.value.id, node.slice.id))
        elif isinstance(node.slice, Tuple):
            return chain.from_iterable(
                (
                    (node.value.id,),
                    (
                        iter(())
                        if node.value.id == "Literal"
                        else map(get_value, map(get_value, node.slice.elts))
                    ),
                )
            )

get_value

get_value(node)

Get the value from a Constant or a Str… or anything with a .value

Parameters:

Name Type Description Default
node Union[Bytes, Constant, Name, Str, UnaryOp]

AST node

required

Returns:

Name Type Description
return_type Optional[Union[str, int, float, bool]]

Probably a string, but could be any constant value

Source code in cdd/shared/ast_utils.py
def get_value(node):
    """
    Get the value from a Constant or a Str… or anything with a `.value`

    :param node: AST node
    :type node: ```Union[Bytes, Constant, Name, Str, UnaryOp]```

    :return: Probably a string, but could be any constant value
    :rtype: ```Optional[Union[str, int, float, bool]]```
    """
    if isinstance(node, (Bytes, Str)):
        return node.s
    elif isinstance(node, Num):
        return node.n
    elif isinstance(node, Constant) or hasattr(node, "value"):
        value = node.value
        return NoneStr if value is None else value
    # elif isinstance(node, (Tuple, Name)):  # It used to be Index in Python < 3.9
    elif isinstance(node, UnaryOp) and isinstance(
        node.operand, (Str, Bytes, Num, Constant, NameConstant)
    ):
        return {"USub": neg, "UAdd": pos, "not_": not_, "Invert": inv}[
            type(node.op).__name__
        ](get_value(node.operand))
    elif isinstance(node, Name):
        return node.id
    else:
        return node

infer_imports

infer_imports(module, modules_to_all=DEFAULT_MODULES_TO_ALL)

Infer imports from AST nodes (Name|.annotation|.type_comment); in order; these: - typing - typing_extensions - collections.abc - sqlalchemy - pydantic

Parameters:

Name Type Description Default
module Union[Module, ClassDef, FunctionDef, AsyncFunctionDef, Assign, AnnAssign]

Module, ClassDef, FunctionDef, AsyncFunctionDef, Assign, AnnAssign

required
modules_to_all tuple[tuple[str, frozenset], ...]

Tuple of module_name to all of module; (str) to FrozenSet[str]

required

Returns:

Name Type Description
return_type Optional[Tuple[Union[Import, ImportFrom], ...]]

List of imports

Source code in cdd/shared/ast_utils.py
def infer_imports(module, modules_to_all=DEFAULT_MODULES_TO_ALL):
    """
    Infer imports from AST nodes (Name|.annotation|.type_comment); in order; these:
      - typing
      - typing_extensions
      - collections.abc
      - sqlalchemy
      - pydantic

    :param module: Module, ClassDef, FunctionDef, AsyncFunctionDef, Assign, AnnAssign
    :type module: ```Union[Module, ClassDef, FunctionDef, AsyncFunctionDef, Assign, AnnAssign]```

    :param modules_to_all: Tuple of module_name to __all__ of module; (str) to FrozenSet[str]
    :type modules_to_all: ```tuple[tuple[str, frozenset], ...]```

    :return: List of imports
    :rtype: ```Optional[Tuple[Union[Import, ImportFrom], ...]]```
    """
    if isinstance(module, (ClassDef, FunctionDef, AsyncFunctionDef, Assign, AnnAssign)):
        module: Module = Module(body=[module], type_ignores=[], stmt=None)
    assert isinstance(module, Module), "Expected `Module` got `{type_name}`".format(
        type_name=type(module).__name__
    )

    def node_to_importable_name(node):
        """
        :param node: AST node
        :type node: ```AST```

        :return: importable typename or None
        :rtype: ```Optional[str]```
        """
        if getattr(node, "type_comment", None) is not None:
            return (
                node.type_comment
                if node.type_comment in simple_types
                else get_value(
                    get_value(get_value(ast.parse(node.type_comment).body[0]))
                )
            )
        elif getattr(node, "annotation", None) is not None:
            node = node  # type: Union[AnnAssign, arg]
            return node.annotation  # cast(node, Union[AnnAssign, arg])
        elif isinstance(node, Name):
            return node.id
        else:
            return None

    _symbol_to_import: Callable[[str], Optional[TTuple[str, str]]] = partial(
        symbol_to_import, modules_to_all=modules_to_all
    )

    # Lots of room for optimisation here; but its probably NP-hard:
    imports = tuple(
        map(
            lambda mod_names: ImportFrom(
                module=mod_names[0],
                names=list(
                    map(
                        lambda name: alias(
                            name,
                            None,
                            identifier=None,
                            identifier_name=None,
                        ),
                        sorted(frozenset(map(itemgetter(0), mod_names[1]))),
                    ),
                ),
                level=0,
                identifier=None,
            ),
            groupby(
                sorted(
                    filter(
                        None,
                        map(
                            # Because there are duplicate names, centralise all import resolution here and order them
                            _symbol_to_import,
                            sorted(
                                frozenset(
                                    chain.from_iterable(
                                        filter(
                                            None,
                                            map(
                                                get_types,
                                                filter(
                                                    None,
                                                    map(
                                                        node_to_importable_name,
                                                        ast.walk(module),
                                                    ),
                                                ),
                                            ),
                                        ),
                                    )
                                )
                            ),
                        ),
                    ),
                    key=itemgetter(1),
                ),
                key=itemgetter(1),
            ),
        )
    )  # type: tuple[ImportFrom, ...]

    # cdd.sqlalchemy.utils.parse_utils.imports_from(sqlalchemy_class_or_assigns)
    return imports if imports else None

infer_type_and_default

infer_type_and_default(action, default, typ, required)

Infer the type string from the default and typ

Parameters:

Name Type Description Default
action Optional[str]

Name of the action

required
default Any

Initial default value

required
typ Optional[str]

The type of the argument

required
required bool

Whether to require the argument

required

Returns:

Name Type Description
return_type tuple[str, Any, bool, str]

action (e.g., for argparse.Action), default, whether its required, inferred type str

Source code in cdd/shared/ast_utils.py
def infer_type_and_default(action, default, typ, required):
    """
    Infer the type string from the default and typ

    :param action: Name of the action
    :type action: ```Optional[str]```

    :param default: Initial default value
    :type default: ```Any```

    :param typ: The type of the argument
    :type typ: ```Optional[str]```

    :param required: Whether to require the argument
    :type required: ```bool```

    :return: action (e.g., for `argparse.Action`), default, whether its required, inferred type str
    :rtype: ```tuple[str, Any, bool, str]```
    """
    if code_quoted(default):
        return _infer_type_and_default_from_quoted(action, default, required, typ)
    elif type(default).__name__ in simple_types:
        typ = type(default).__name__
    elif isinstance(default, AST):
        action, default, required, typ = _parse_default_from_ast(
            action, default, required, typ
        )
    elif hasattr(default, "__str__") and str(default) == "<required parameter>":
        # Special type that PyTorch uses & defines
        action, default, required, typ = None, None, True, default.__class__.__name__
    elif isinstance(default, (list, tuple, set)):
        # `set` actually won't marshall back properly as JSON/YAML doesn't support this type :(
        action, default, required, typ = _infer_type_and_default_for_list_or_tuple(
            action, tuple(default) if isinstance(default, set) else default, required
        )
    elif isinstance(default, dict):
        typ = "loads"
        try:
            default = dumps(default)
        except TypeError:
            # YAML is more permissive though less concise, but `loads` from yaml is used so this works
            default = (
                dumps(default, ensure_ascii=False)
                if safe_dump_all is None
                else safe_dump_all(default)
            )
    elif default is None:
        if "Optional" not in (typ or iter(())) and typ not in frozenset(
            ("Any", "pickle.loads", "loads")
        ):
            typ = None
    elif any(
        (
            isinstance(default, type),
            isfunction(default),
            isclass(default),
            contains(
                frozenset(
                    map(
                        "tf.{}".format,
                        (
                            "qint16",
                            "qint16_ref",
                            "qint32",
                            "qint32_ref",
                            "qint8",
                            "qint8_ref",
                            "quint16",
                            "quint16_ref",
                            "quint8",
                            "quint8_ref",
                            "bfloat16",
                            "bool",
                            "complex128",
                            "complex64",
                            "double",
                            "float16",
                            "float32",
                            "float64",
                            "half",
                            "int16",
                            "int32",
                            "int64",
                            "int8",
                            "qint16",
                            "qint32",
                            "qint8",
                            "quint16",
                            "quint8",
                            "resource",
                            "string",
                            "uint16",
                            "uint32",
                            "uint64",
                            "uint8",
                            "variant",
                        ),
                    )
                ),
                repr(default),
            ),
        )
    ):
        typ, default, required = "pickle.loads", pickle.dumps(default), False
    else:
        raise NotImplementedError(
            "Parsing type {default_type!s}, which contains {default!r}".format(
                default_type=type(default), default=default
            )
        )

    return action, default, required, typ

is_argparse_add_argument

is_argparse_add_argument(node)

Checks if AST node is a call to argument_parser.add_argument

Parameters:

Name Type Description Default
node AST

AST node

required

Returns:

Name Type Description
return_type bool

Whether the input is the call to argument_parser.add_argument

Source code in cdd/shared/ast_utils.py
def is_argparse_add_argument(node):
    """
    Checks if AST node is a call to `argument_parser.add_argument`

    :param node: AST node
    :type node: ```AST```

    :return: Whether the input is the call to `argument_parser.add_argument`
    :rtype: ```bool```
    """
    return (
        isinstance(node, Expr)
        and isinstance(node.value, Call)
        and isinstance(node.value.func, Attribute)
        and node.value.func.attr == "add_argument"
        and isinstance(node.value.func.value, Name)
        and node.value.func.value.id == "argument_parser"
    )

is_argparse_description

is_argparse_description(node)

Checks if AST node is argument_parser.description

Parameters:

Name Type Description Default
node AST

AST node

required

Returns:

Name Type Description
return_type bool

Whether the input is the call to argument_parser.description

Source code in cdd/shared/ast_utils.py
def is_argparse_description(node):
    """
    Checks if AST node is `argument_parser.description`

    :param node: AST node
    :type node: ```AST```

    :return: Whether the input is the call to `argument_parser.description`
    :rtype: ```bool```
    """
    return (
        isinstance(node, Assign)
        and len(node.targets) == 1
        and isinstance(node.targets[0], Attribute)
        and node.targets[0].attr == "description"
        and isinstance(node.targets[0].value, Name)
        and node.targets[0].value.id == "argument_parser"
        and isinstance(node.value, (Constant, Str))
    )

it2literal

it2literal(it)

Convert a collection of constants into a type annotation

Parameters:

Name Type Description Default
it Union[tuple[Union[str, int, float], ...], list[Union[str, int, float], ...]]

collection of constants

required

Returns:

Name Type Description
return_type Subscript

Subscript Literal for annotation

Source code in cdd/shared/ast_utils.py
def it2literal(it):
    """
    Convert a collection of constants into a type annotation

    :param it: collection of constants
    :type it: ```Union[tuple[Union[str, int, float], ...], list[Union[str, int, float], ...]]```

    :return: Subscript Literal for annotation
    :rtype: ```Subscript```
    """
    return Subscript(
        Name("Literal", Load(), lineno=None, col_offset=None),
        Index(
            value=(
                Tuple(
                    ctx=Load(),
                    elts=list(map(set_value, it)),
                    expr=None,
                    lineno=None,
                    col_offset=None,
                )
                if len(it) > 1
                else set_value(it[0])
            )
        ),
        Load(),
        lineno=None,
        col_offset=None,
    )

merge_assignment_lists

merge_assignment_lists(node, name, unique_sort=True)

Merge multiple same-name lists within the body of a node into one, e.g., if you have multiple __all__

Parameters:

Name Type Description Default
node Union[Module, ClassDef, FunctionDef, If, Try, While, With, AsyncFor, AsyncFunctionDef, AsyncWith, ExceptHandler, Expression, For, IfExp, Interactive, Lambda ]

AST node with a '.body'

required
name id

Name to match (matches against id field of Name)

required
unique_sort bool

Whether to ensure its unique + sorted

required
Source code in cdd/shared/ast_utils.py
def merge_assignment_lists(node, name, unique_sort=True):
    """
    Merge multiple same-name lists within the body of a node into one, e.g., if you have multiple ```__all__```

    :param node: AST node with a '.body'
    :type node: ```Union[Module, ClassDef, FunctionDef, If, Try, While, With, AsyncFor, AsyncFunctionDef, AsyncWith,
                         ExceptHandler, Expression, For, IfExp, Interactive, Lambda ]```

    :param name: Name to match (matches against `id` field of `Name`)
    :type name: ```str```

    :param unique_sort: Whether to ensure its unique + sorted
    :type unique_sort: ```bool```
    """
    asses = tuple(get_ass_where_name(node, name))

    # if len(asses) < 2: return

    # Could extract the `AnnAssign` stuff I suppose…

    del_ass_where_name(node, name)
    elts = map(
        get_value,
        chain.from_iterable(
            map(
                attrgetter("elts"),
                asses,
            )
        ),
    )
    node.body.append(
        Assign(
            targets=[Name("__all__", Store(), lineno=None, col_offset=None)],
            value=List(
                ctx=Load(),
                elts=list(
                    map(set_value, (sorted(frozenset(elts)) if unique_sort else elts))
                ),
                expr=None,
            ),
            expr=None,
            lineno=None,
            **maybe_type_comment,
        )
    )

merge_modules

merge_modules(mod0, mod1, remove_imports_from_second=True, deduplicate_names=False)

Merge modules (removing module docstring from mod1)

Parameters:

Name Type Description Default
mod0 Module

Module

required
mod1 Module

Module

required
remove_imports_from_second bool

Whether to remove global imports from second module

required
deduplicate_names bool

Whether to deduplicate names; names can be function|class|AnnAssign|Assign name

required

Returns:

Name Type Description
return_type Module

Merged module (copy)

Source code in cdd/shared/ast_utils.py
def merge_modules(mod0, mod1, remove_imports_from_second=True, deduplicate_names=False):
    """
    Merge modules (removing module docstring from mod1)

    :param mod0: Module
    :type mod0: ```Module```

    :param mod1: Module
    :type mod1: ```Module```

    :param remove_imports_from_second: Whether to remove global imports from second module
    :type remove_imports_from_second: ```bool```

    :param deduplicate_names: Whether to deduplicate names; names can be function|class|AnnAssign|Assign name
    :type deduplicate_names: ```bool```

    :return: Merged module (copy)
    :rtype: ```Module```
    """
    mod1_body = (
        mod1.body[1:]
        if mod1.body and isinstance(get_value(mod1.body[0]), (Str, Constant))
        else mod1.body
    )

    new_mod = deepcopy(mod0)

    new_mod.body += (
        list(
            filterfalse(
                rpartial(isinstance, (ImportFrom, Import)),
                mod1_body,
            )
        )
        if remove_imports_from_second
        else deepcopy(mod1_body)
    )
    # if deduplicate_names:
    #
    #     def unique_nodes(node):
    #         """
    #         :param node: AST node
    #         :type node: ```AST```
    #
    #         :return: node if name is in `seen` set else None; with side-effect of adding to `seen`
    #         :rtype: ```bool```
    #         """
    #
    #         def side_effect_ret(name):
    #             """
    #             :param name: Name
    #             :type name: ```str```
    #
    #             :return: node if name is in `seen` set else None; with side-effect of adding to `seen`
    #             :rtype: ```bool```
    #             """
    #             if name in seen:
    #                 return None
    #             else:
    #                 seen.add(node.name)
    #                 return node
    #
    #         if isinstance(node, (FunctionDef, AsyncFunctionDef, ClassDef)):
    #             return side_effect_ret(node.name)
    #         elif isinstance(node, AnnAssign):
    #             return side_effect_ret(get_value(node.target))
    #         elif isinstance(node, Assign):
    #             return any(
    #                 filter(
    #                     lambda target: side_effect_ret(get_value(target)), node.targets
    #                 )
    #             )
    #         else:
    #             return node
    #
    #     seen = set()
    #     new_mod.body = list(filter(None, map(unique_nodes, new_mod.body)))

    return new_mod

module_to_all

module_to_all(module_or_filepath)

From input, create (("module_name", {"symbol0", "symbol1"}),)

Parameters:

Name Type Description Default
module_or_filepath Union[str, Module]

Module or filepath

required

Returns:

Name Type Description
return_type List[str]

__all__ from module (if present) else all symbols in module

Source code in cdd/shared/ast_utils.py
def module_to_all(module_or_filepath):
    """
    From input, create (("module_name", {"symbol0", "symbol1"}),)

    :param module_or_filepath: Module or filepath
    :type module_or_filepath: ```Union[str, Module]```

    :return: `__all__` from module (if present) else all symbols in module
    :rtype: ```List[str]```
    """
    assert isinstance(module_or_filepath, (str, Module))
    if not path.exists(module_or_filepath):
        module_or_filepath = find_module_filepath(module_or_filepath)

    with open(module_or_filepath, "rt") as f:
        module_or_filepath: Module = cdd.shared.source_transformer.ast_parse(
            f.read(), filename=module_or_filepath
        )

    module_or_filepath: Module = module_or_filepath

    # If exists, construct `list[str]` version of `__all__`
    all_ = list(
        map(
            get_value,
            chain.from_iterable(
                map(
                    attrgetter("elts"),
                    map(get_value, get_ass_where_name(module_or_filepath, "__all__")),
                )
            ),
        )
    )

    return (
        all_
        if all_
        else list(chain.from_iterable(map(get_names, module_or_filepath.body)))
    )

node_to_dict

node_to_dict(node)

Convert AST node to a dict

Parameters:

Name Type Description Default
node AST

AST node

required

Returns:

Name Type Description
return_type dict

Dict representation

Source code in cdd/shared/ast_utils.py
def node_to_dict(node):
    """
    Convert AST node to a dict

    :param node: AST node
    :type node: ```AST```

    :return: Dict representation
    :rtype: ```dict```
    """
    return {
        attr: (
            lambda val: (
                type(val)(map(get_value, val))
                if isinstance(val, (tuple, list))
                else get_value(val)
            )
        )(getattr(node, attr))
        for attr in dir(node)
        if not attr.startswith("_") and not attr.endswith(("lineno", "offset"))
    }

optimise_imports

optimise_imports(imports)

Optimise imports involves: - Deduplication of module names - Deduplication of symbols import from module names

For more complicated set-ups I recommend use of: - autoflake --remove-all-unused-imports - isort

Parameters:

Name Type Description Default
imports Iterable[ImportFrom]

ImportFrom nodes

required

Returns:

Name Type Description
return_type list[ImportFrom]

ImportFrom nodes

Source code in cdd/shared/ast_utils.py
def optimise_imports(imports):
    """
    Optimise imports involves:
    - Deduplication of module names
    - Deduplication of symbols import from module names

    For more complicated set-ups I recommend use of:
    - autoflake --remove-all-unused-imports
    - isort

    :param imports: `ImportFrom` nodes
    :type imports: ```Iterable[ImportFrom]```

    :return: `ImportFrom` nodes
    :rtype: ```list[ImportFrom]```
    """
    seen_pair = set()
    return [
        ImportFrom(
            module=module_name,
            level=sym[0].level,
            names=list(
                map(
                    lambda al: alias(
                        name=al.name,
                        asname=al.asname,
                        identifier=None,
                        identifier_name=None,
                    ),
                    sym[1],
                )
            ),
        )
        for module_name, symbols in map(
            lambda key_group: (
                key_group[0],
                filter(
                    None,
                    map(
                        lambda node: (
                            lambda filtered: (
                                (
                                    namedtuple("_", ("level",))(node.level),
                                    filtered,
                                )
                                if filtered
                                else None
                            )
                        )(
                            tuple(
                                filter(
                                    None,
                                    map(
                                        lambda name_asname_key: (
                                            None
                                            if name_asname_key.key in seen_pair
                                            else (
                                                seen_pair.add(name_asname_key.key)
                                                or namedtuple("_", ("name", "asname"))(
                                                    name_asname_key.name,
                                                    name_asname_key.asname,
                                                )
                                            )
                                        ),
                                        map(
                                            lambda _alias: namedtuple(
                                                "_", ("name", "asname", "key")
                                            )(
                                                _alias.name,
                                                _alias.asname,
                                                "{}{}{}".format(
                                                    key_group[0],
                                                    _alias.name,
                                                    _alias.asname,
                                                ),
                                            ),
                                            node.names,
                                        ),
                                    ),
                                )
                            )
                        ),
                        key_group[1],
                    ),
                ),
            ),
            groupby(
                sorted(
                    imports,
                    key=attrgetter("module"),
                ),
                key=attrgetter("module"),
            ),
        )
        for sym in symbols
    ]

param2argparse_param

param2argparse_param(param, word_wrap=True, emit_default_doc=True)

Converts a param to an Expr argparse.add_argument call

Parameters:

Name Type Description Default
param dict

Name, dict with keys: 'typ', 'doc', 'default'

required
word_wrap bool

Whether to word-wrap. Set DOCTRANS_LINE_LENGTH to configure length.

required
emit_default_doc bool

Whether help/docstring should include 'With default' text

required

Returns:

Name Type Description
return_type Expr

argparse.add_argument call—with arguments—as an AST node

Source code in cdd/shared/ast_utils.py
def param2argparse_param(param, word_wrap=True, emit_default_doc=True):
    """
    Converts a param to an Expr `argparse.add_argument` call

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

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

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

    :return: `argparse.add_argument` call—with arguments—as an AST node
    :rtype: ```Expr```
    """
    name, _param = param
    del param
    typ, choices, required, action = (
        "str",
        None,
        _param.get("default") is not None,
        None,
    )
    _param.setdefault("typ", "Any")
    action, choices, required, typ, (name, _param) = _resolve_arg(
        action, choices, (name, _param), required, typ
    )
    # is_kwarg = param[0].endswith("kwargs")

    _param.setdefault("doc", "")
    doc, _default = extract_default(_param["doc"], emit_default_doc=emit_default_doc)
    _action, default, _required, _typ = infer_type_and_default(
        action,
        _param.get("default", _default),
        typ,
        required=required,
        # _default, _param, action, required, typ#
    )
    if default is None and _param.get("default") == NoneStr:
        required = False
    if _action:
        action = _action
    if _typ is not None:
        typ = _typ
    if typ == "pickle.loads":
        required = False
    elif typ == "str" and action is None:
        typ = None  # Because `str` is default anyway

    return Expr(
        Call(
            args=[set_value("--{name}".format(name=name))],
            func=Attribute(
                Name("argument_parser", Load(), lineno=None, col_offset=None),
                "add_argument",
                Load(),
                lineno=None,
                col_offset=None,
            ),
            keywords=list(
                filter(
                    None,
                    (
                        (
                            typ
                            if typ is None
                            else keyword(
                                arg="type",
                                value=(
                                    FALLBACK_ARGPARSE_TYP
                                    if typ == "globals().__getitem__"
                                    else Name(typ, Load(), lineno=None, col_offset=None)
                                ),
                                identifier=None,
                            )
                        ),
                        (
                            choices
                            if choices is None
                            else keyword(
                                arg="choices",
                                value=Tuple(
                                    ctx=Load(),
                                    elts=list(map(set_value, choices)),
                                    expr=None,
                                    lineno=None,
                                    col_offset=None,
                                ),
                                identifier=None,
                            )
                        ),
                        (
                            action
                            if action is None
                            else keyword(
                                arg="action",
                                value=set_value(action),
                                identifier=None,
                            )
                        ),
                        (
                            keyword(
                                arg="help",
                                value=set_value((fill if word_wrap else identity)(doc)),
                                identifier=None,
                            )
                            if doc
                            else None
                        ),
                        (
                            keyword(
                                arg="required",
                                value=set_value(True),
                                identifier=None,
                            )
                            if required is True
                            else None
                        ),
                        (
                            default
                            if default is None
                            else keyword(
                                arg="default",
                                value=set_value(default),
                                identifier=None,
                            )
                        ),
                    ),
                )
            ),
            expr=None,
            expr_func=None,
            lineno=None,
            col_offset=None,
        ),
        lineno=None,
        col_offset=None,
    )

param2ast

param2ast(param)

Converts a param to an AnnAssign

Parameters:

Name Type Description Default
param dict

Name, dict with keys: 'typ', 'doc', 'default'

required

Returns:

Name Type Description
return_type Union[AnnAssign, Assign]

AST node for assignment

Source code in cdd/shared/ast_utils.py
def param2ast(param):
    """
    Converts a param to an AnnAssign

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

    :return: AST node for assignment
    :rtype: ```Union[AnnAssign, Assign]```
    """
    name, _param = param
    del param

    def get_default_val(val):
        """
        retrieve default val for application to `.value` of `Assign | AnnAssign`

        :param val: value to retrieve default val for
        :type val: ```Optional[str]```

        :return: default val for application to `.value` of `Assign | AnnAssign`
        :rtype: ```Optional[str]```
        """
        return None if val is None else set_value(None if val == NoneStr else val)

    if "default" in _param:
        if isinstance(_param["default"], (Constant, Str, NameConstant, Num)):
            _param["default"] = get_value(_param["default"])
        if _param.get("typ") is None and not getattr(
            _param["default"], "__contains__", iter(())
        )("["):
            _param["typ"] = (
                "Optional[Any]"
                if _param["default"] == NoneStr
                else type(_param["default"]).__name__
            )
        elif _param.get("typ") == "Str":
            _param["typ"] = "str"
        elif _param.get("typ") in frozenset(("Constant", "NameConstant", "Num")):
            _param["typ"] = "object"
    if "typ" in _param and needs_quoting(_param["typ"]):
        default = (
            _param.get("default")
            if _param.get("default") in (None, NoneStr)
            else quote(_param["default"])
        )
        return AnnAssign(
            annotation=(
                Name(_param["typ"], Load(), lineno=None, col_offset=None)
                if _param["typ"] in simple_types
                else get_value(ast.parse(_param["typ"]).body[0])
            ),
            simple=1,
            target=Name(name, Store(), lineno=None, col_offset=None),
            value=get_default_val(default),
            expr=None,
            expr_target=None,
            expr_annotation=None,
            col_offset=None,
            lineno=None,
        )
    if _param.get("typ") is None:
        return Assign(
            annotation=None,
            simple=1,
            targets=[Name(name, Store(), lineno=None, col_offset=None)],
            value=(lambda val: set_value(val) if val is None else val)(
                get_default_val(_param.get("default"))
            ),
            expr=None,
            expr_target=None,
            expr_annotation=None,
            lineno=None,
            col_offset=None,
            **maybe_type_comment,
        )
    elif _param["typ"] in simple_types:
        return AnnAssign(
            annotation=Name(_param["typ"], Load(), lineno=None, col_offset=None),
            simple=1,
            target=Name(name, Store(), lineno=None, col_offset=None),
            value=get_default_val(_param.get("default")),
            expr=None,
            expr_target=None,
            expr_annotation=None,
            col_offset=None,
            lineno=None,
        )
    elif _param["typ"] == "dict" or _param["typ"].startswith("*"):
        return AnnAssign(
            annotation=set_slice(Name("dict", Load(), lineno=None, col_offset=None)),
            simple=1,
            target=Name(name, Store(), lineno=None, col_offset=None),
            value=Dict(keys=[], values=_param.get("default", []), expr=None),
            expr=None,
            expr_target=None,
            expr_annotation=None,
            col_offset=None,
            lineno=None,
        )
    else:
        return _generic_param2ast((name, _param))

parse_to_scalar

parse_to_scalar(node)

Parse the input to a scalar

Parameters:

Name Type Description Default
node Any

Any value

required

Returns:

Name Type Description
return_type Union[str, int, float, complex, None]

Scalar

Source code in cdd/shared/ast_utils.py
def parse_to_scalar(node):
    """
    Parse the input to a scalar

    :param node: Any value
    :type node: ```Any```

    :return: Scalar
    :rtype: ```Union[str, int, float, complex, None]```
    """
    if isinstance(node, (int, float, complex, str, type(None))):
        return node
    elif isinstance(node, (Constant, Expr, Str, Num)):
        return get_value(node)
    elif isinstance(node, ast.AST):
        return _to_code(node).rstrip("\n")
    else:
        raise NotImplementedError(
            "Converting this to scalar: {node!r}".format(node=node)
        )

set_arg

set_arg(arg, annotation=None)

In Python 3.8 expr and type_comment need to be set on arg. This function handles constructs an ast.arg handling that issue.

Parameters:

Name Type Description Default
arg Optional[str]

The argument name

required
annotation Optional[ast.AST]

The argument's annotation

required

Returns:

Name Type Description
return_type ast.arg

The constructed ast.arg

Source code in cdd/shared/ast_utils.py
def set_arg(arg, annotation=None):
    """
    In Python 3.8 `expr` and `type_comment` need to be set on arg.
    This function handles constructs an `ast.arg` handling that issue.

    :param arg: The argument name
    :type arg: ```Optional[str]```

    :param annotation: The argument's annotation
    :type annotation: ```Optional[ast.AST]```

    :return: The constructed ```ast.arg```
    :rtype: ```ast.arg```
    """
    return ast.arg(
        arg=arg,
        annotation=annotation,
        identifier_arg=None,
        lineno=None,
        col_offset=None,
        **dict(expr=None, **maybe_type_comment) if PY_GTE_3_8 else {},
    )

set_docstring

set_docstring(doc_str, empty, node)

Set docstring on node that can have a docstring. If doc_str is empty, the doc_str node is removed.

Parameters:

Name Type Description Default
doc_str Optional[str]

Docstring

required
empty bool

Whether the doc_str is empty (micro-optimization)

required
node Union[Module, AsyncFunctionDef, FunctionDef, ClassDef]

AST node to set the docstring on

required
Source code in cdd/shared/ast_utils.py
def set_docstring(doc_str, empty, node):
    """
    Set docstring on node that can have a docstring. If doc_str is empty, the doc_str node is removed.

    :param doc_str: Docstring
    :type doc_str: ```Optional[str]```

    :param empty: Whether the doc_str is empty (micro-optimization)
    :type empty: ```bool```

    :param node: AST node to set the docstring on
    :type node: ```Union[Module, AsyncFunctionDef, FunctionDef, ClassDef]```
    """
    (
        node.body.__setitem__
        if isinstance(node.body[0], Expr)
        and isinstance(get_value(node.body[0].value), str)
        else node.body.insert
    )(
        0,
        Expr(set_value(doc_str), lineno=None, col_offset=None),
    )
    if empty or get_value(node.body[0].value).isspace():
        del node.body[0]

set_slice

set_slice(node)

In Python 3.9 there's a new ast parser (PEG) that no longer wraps things in Index. This function handles this issue.

Parameters:

Name Type Description Default
node ast.AST

An AST node

required

Returns:

Name Type Description
return_type Union[ast.AST, Index]

Original node, possibly wrapped in an Index

Source code in cdd/shared/ast_utils.py
def set_slice(node):
    """
    In Python 3.9 there's a new ast parser (PEG) that no longer wraps things in Index.
    This function handles this issue.

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

    :return: Original node, possibly wrapped in an ```Index```
    :rtype: ```Union[ast.AST, Index]```
    """
    return node if PY_GTE_3_9 else Index(node)

set_value

set_value(value, kind=None)

Creates a Constant on Python >= 3.8 otherwise more specific AST type

Parameters:

Name Type Description Default
value Any

AST node

required
kind Optional[Any]

AST node

required

Returns:

Name Type Description
return_type Union[Constant, Num, Str, NameConstant]

Probably a string, but could be any constant value

Source code in cdd/shared/ast_utils.py
def set_value(value, kind=None):
    """
    Creates a `Constant` on Python >= 3.8 otherwise more specific AST type

    :param value: AST node
    :type value: ```Any```

    :param kind: AST node
    :type kind: ```Optional[Any]```

    :return: Probably a string, but could be any constant value
    :rtype: ```Union[Constant, Num, Str, NameConstant]```
    """
    if (
        value is not None
        and isinstance(value, str)
        and len(value) > 2
        and value[0] + value[-1] in frozenset(('""', "''"))
    ):
        value = value[1:-1]
    return (
        Constant(kind=kind, value=value, constant_value=None, string=None)
        if PY_GTE_3_8
        else (
            Str(s=value, constant_value=None, string=None, col_offset=None, lineno=None)
            if isinstance(value, str)
            else (
                Num(n=value, constant_value=None, string=None)
                if not isinstance(value, bool)
                and isinstance(value, (int, float, complex))
                else NameConstant(
                    value=value,
                    constant_value=None,
                    string=None,
                    lineno=None,
                    col_offset=None,
                )
            )
        )
    )

symbol_to_import

symbol_to_import(symbol, modules_to_all)

Resolve symbol to module

Parameters:

Name Type Description Default
symbol str

symbol to look for within various modules

required
modules_to_all tuple[tuple[str, frozenset], ...]

Tuple of module_name to all of module; (str) to FrozenSet[str]

required

Returns:

Name Type Description
return_type Optional[Tuple[str, str]]

(symbol, module) if name in module else None

Source code in cdd/shared/ast_utils.py
def symbol_to_import(
    symbol,
    modules_to_all,
):
    """
    Resolve symbol to module

    :param symbol: symbol to look for within various modules
    :type symbol: ```str```

    :param modules_to_all: Tuple of module_name to __all__ of module; (str) to FrozenSet[str]
    :type modules_to_all: ```tuple[tuple[str, frozenset], ...]```

    :return: (symbol, module) if name in module else None
    :rtype: ```Optional[Tuple[str, str]]```
    """
    return next(
        ((symbol, module) for (module, all_) in modules_to_all if symbol in all_), None
    )

to_annotation

to_annotation(typ)

Converts the typ to an annotation

Parameters:

Name Type Description Default
typ str

A string representation of the type to annotate with. Else return give identity.

required

Returns:

Name Type Description
return_type AST

The annotation as a Name (usually) or else some more complex type

Source code in cdd/shared/ast_utils.py
def to_annotation(typ):
    """
    Converts the typ to an annotation

    :param typ: A string representation of the type to annotate with. Else return give identity.
    :type typ: ```Union[str, AST]```

    :return: The annotation as a `Name` (usually) or else some more complex type
    :rtype: ```AST```
    """
    if isinstance(typ, AST):
        return typ
    return (
        None
        if typ in none_types
        else (
            Name(typ, Load(), lineno=None, col_offset=None)
            if typ in simple_types
            else get_value(
                (
                    lambda parsed: (
                        parsed.body[0] if getattr(parsed, "body", None) else parsed
                    )
                )(ast.parse(typ))
            )
        )
    )

to_type_comment

to_type_comment(node)

Convert annotation to a type comment

Parameters:

Name Type Description Default
node str

AST node with a '.annotation' or Name or str

required

Returns:

Name Type Description
return_type str

type_comment

Source code in cdd/shared/ast_utils.py
def to_type_comment(node):
    """
    Convert annotation to a type comment

    :param node: AST node with a '.annotation' or Name or str
    :type node: ```Union[Name, str, AnnAssign, arg, arguments]```

    :return: type_comment
    :rtype: ```str```
    """
    return (
        node.id
        if isinstance(node, Name)
        else (
            node
            if isinstance(node, str) or not hasattr(node, "annotation")
            else _to_code(node.annotation).strip()
        )
    )

cdd.shared.conformance

cdd.shared.conformance

Given the truth, show others the path

Parameters:

Name Type Description Default

ground_truth

ground_truth(args, truth_file)

There is but one truth. Conform.

Parameters:

Name Type Description Default
args Namespace

Namespace with the values of the CLI arguments

required
truth_file str

contains the filename of the one true source

required

Returns:

Name Type Description
return_type OrderedDict

Filenames and whether they were changed

Source code in cdd/shared/conformance.py
def ground_truth(args, truth_file):
    """
    There is but one truth. Conform.

    :param args: Namespace with the values of the CLI arguments
    :type args: ```Namespace```

    :param truth_file: contains the filename of the one true source
    :type truth_file: ```str```

    :return: Filenames and whether they were changed
    :rtype: ```OrderedDict```
    """
    arg2parse_emit_type = {
        "argparse_function": (
            cdd.argparse_function.parse.argparse_ast,
            cdd.argparse_function.emit.argparse_function,
            FunctionDef,
        ),
        "class": (cdd.class_.parse.class_, cdd.class_.emit.class_, ClassDef),
        "function": (
            cdd.function.parse.function,
            cdd.function.emit.function,
            FunctionDef,
        ),
    }

    parse_func, emit_func, type_wanted = arg2parse_emit_type[args.truth]
    search: List[str] = _get_name_from_namespace(args, args.truth).split(".")

    with open(truth_file, "rt") as f:
        true_ast = ast_parse(f.read(), filename=truth_file)

    original_node = find_in_ast(search, true_ast)
    gold_ir: IntermediateRepr = parse_func(
        original_node,
        **_default_options(node=original_node, search=search, type_wanted=type_wanted)()
    )

    effect = OrderedDict()
    # filter(lambda arg: arg != args.truth, arg2parse_emit_type.keys()):
    for fun_name, (parse_func, emit_func, type_wanted) in arg2parse_emit_type.items():
        search = list(strip_split(_get_name_from_namespace(args, fun_name), "."))

        filenames = getattr(args, pluralise(fun_name))
        assert isinstance(
            filenames, (list, tuple)
        ), "Expected `Union[list, tuple]` got `{type_name}`".format(
            type_name=type(filenames).__name__
        )

        effect.update(
            map(
                lambda filename: _conform_filename(
                    filename=filename,
                    search=search,
                    emit_func=partial(emit_func, word_wrap=args.no_word_wrap is None),
                    replacement_node_ir=gold_ir,
                    type_wanted=type_wanted,
                ),
                filenames,
            )
        )

    return effect

cdd.shared.cst

cdd.shared.cst

Concrete Syntax Tree for Python 3.6+ source code

Parameters:

Name Type Description Default

cst_parse

cst_parse(source)

Parse Python source lines into a Concrete Syntax Tree

Parameters:

Name Type Description Default
source str

Python source code

required

Returns:

Name Type Description
return_type list[Any]

List of namedtuples with at least ("line_no_start", "line_no_end", "value") attributes

Source code in cdd/shared/cst.py
def cst_parse(source):
    """
    Parse Python source lines into a Concrete Syntax Tree

    :param source: Python source code
    :type source: ```str```

    :return: List of `namedtuple`s with at least ("line_no_start", "line_no_end", "value") attributes
    :rtype: ```list[Any]```
    """
    scanned = cst_scanner(source)
    parsed = cst_parser(scanned)
    return parsed

cdd.shared.cst_utils

cdd.shared.cst_utils

Concrete Syntax Tree utility functions

Parameters:

Name Type Description Default

cst_parse_one_node

cst_parse_one_node(statement, state)

Parse one statement into a CST node

Parameters:

Name Type Description Default
statement str

Statement that was scanned from Python source code

required
state dict

Number of lines between runs in acc key; prev_node; parsed

required

Returns:

Name Type Description
return_type NamedTuple

NamedTuple with at least ("line_no_start", "line_no_end", "value") attributes

Source code in cdd/shared/cst_utils.py
@set_prev_node
def cst_parse_one_node(statement, state):
    """
    Parse one statement into a CST node

    :param statement: Statement that was scanned from Python source code
    :type statement: ```str```

    :param state: Number of lines between runs in `acc` key; `prev_node`; `parsed`
    :type state: ```dict```

    :return: NamedTuple with at least ("line_no_start", "line_no_end", "value") attributes
    :rtype: ```NamedTuple```
    """
    prev_acc = state["acc"]
    state["acc"] += statement.count("\n")

    statement_stripped = statement.strip()

    words = tuple(filter(None, map(str.strip, statement_stripped.split(" "))))

    common_kwargs = dict(
        line_no_start=prev_acc,
        line_no_end=state["acc"],
        value=statement,
    )

    if len(statement_stripped) > 5:
        is_single_quote = (statement_stripped[:3], statement_stripped[-3:]) == (
            "'''",
            "'''",
        )
        is_double_quote = not is_single_quote and (
            statement_stripped[:3],
            statement_stripped[-3:],
        ) == (
            '"""',
            '"""',
        )
        if is_single_quote or is_double_quote:
            return TripleQuoted(
                **common_kwargs,
                is_double_q=is_double_quote,
                is_docstr=isinstance(
                    state["prev_node"], (ClassDefinitionStart, FunctionDefinitionStart)
                ),
            )

    if words:
        if len(words) > 1:
            name: Optional[str] = get_construct_name(words)
            if name is not None:
                return (
                    ClassDefinitionStart
                    if words[0] == "class"
                    else FunctionDefinitionStart
                )(**common_kwargs, name=name)

        return infer_cst_type(statement_stripped, words)(**common_kwargs)

    return UnchangingLine(**common_kwargs)

cst_parser

cst_parser(scanned)

Checks if what has been scanned (stack) is ready for the scanned array Add then clear stack if ready else do nothing with both

Parameters:

Name Type Description Default
scanned list[str]

List of statements observed

required

Returns:

Name Type Description
return_type tuple[NamedTuple]

Parse of scanned statements. One dimensional.

Source code in cdd/shared/cst_utils.py
def cst_parser(scanned):
    """
    Checks if what has been scanned (stack) is ready for the `scanned` array
    Add then clear stack if ready else do nothing with both

    :param scanned: List of statements observed
    :type scanned: ```list[str]```

    :return: Parse of scanned statements. One dimensional.
    :rtype: ```tuple[NamedTuple]```
    """
    state = {
        "acc": 1,
        "prev_node": UnchangingLine(line_no_start=None, line_no_end=None, value=""),
        "parsed": [],
    }
    # return accumulate(scanned, partial(cst_parse_one_node, state=state), initial=[])
    deque(map(partial(cst_parse_one_node, state=state), scanned), maxlen=0)
    return tuple(state["parsed"])

cst_scan

cst_scan(scanned, stack)

Checks if what has been scanned (stack) is ready for the scanned array Add then clear stack if ready else do nothing with both

Parameters:

Name Type Description Default
scanned list[str]

List of statements observed

required
stack list[str]

List of characters observed

required
Source code in cdd/shared/cst_utils.py
def cst_scan(scanned, stack):
    """
    Checks if what has been scanned (stack) is ready for the `scanned` array
    Add then clear stack if ready else do nothing with both

    :param scanned: List of statements observed
    :type scanned: ```list[str]```

    :param stack: List of characters observed
    :type stack: ```list[str]```
    """
    statement = "".join(stack)
    statement_stripped = statement.strip()

    is_comment = statement_stripped.startswith("#")
    if statement_stripped.endswith("\\"):
        has_triple_quotes = is_other_statement = False
    else:
        has_triple_quotes = is_triple_quoted(statement_stripped)
        is_other_statement = all(
            (
                statement_stripped,
                balanced_parentheses(statement_stripped),
                not statement_stripped.startswith("@")
                or statement_stripped.endswith(":"),
                not statement_stripped.startswith("'''"),
                not statement_stripped.startswith('"""'),
            )
        )

    statement_found = is_comment or has_triple_quotes or is_other_statement
    if statement_found:
        if is_comment:
            scanned.append(statement)
            stack.clear()
        elif is_other_statement:
            expression = []

            def add_and_clear(the_expression_str, expr, scanned_tokens, the_stack):
                """
                Add the found statement and clear the stacks

                :param the_expression_str: Expression string
                :type the_expression_str: ```str```

                :param expr: The expression
                :type expr: ```list[str]```

                :param scanned_tokens: Scanned tokens
                :type scanned_tokens: ```list[str]```

                :param the_stack: The current stack
                :type the_stack: ```list[str]```

                """
                scanned_tokens.append(the_expression_str)
                expr.clear()
                the_stack.clear()

            expression_str: str = ""
            for idx, ch in enumerate(statement):
                expression.append(ch)
                expression_str: str = "".join(expression)
                expression_stripped = expression_str.strip()

                if (
                    is_triple_quoted(expression_stripped)
                    or expression_stripped.startswith("#")
                    and expression_str.endswith("\n")
                ):
                    add_and_clear(expression_str, expression, scanned, stack)
                    # Single comment should be picked up
                elif balanced_parentheses(expression_stripped):
                    if (
                        expression_str.endswith("\n")
                        and not expression_stripped.endswith("\\")
                        and (
                            not expression_stripped.endswith(":")
                            or "class" not in expression_stripped
                            and "def" not in expression_stripped
                        )
                        and not expression_str.isspace()
                        and not expression_stripped.startswith("@")
                    ):
                        add_and_clear(expression_str, expression, scanned, stack)
                    else:
                        words = tuple(
                            filter(None, map(str.strip, expression_stripped.split(" ")))
                        )
                        if "def" in words or "class" in words:
                            if words[-1].endswith(":") and balanced_parentheses(
                                expression_stripped
                            ):
                                add_and_clear(
                                    expression_str, expression, scanned, stack
                                )
                        # elif ";"
            if expression:
                add_and_clear(expression_str, expression, scanned, stack)
            # Longest matching pattern should be parsed out but this does shortest^
        else:
            scanned.append(statement)
            stack.clear()

cst_scanner

cst_scanner(source)

Reduce source code into chunks useful for parsing. These chunks include newlines and the array is one dimensional.

Parameters:

Name Type Description Default
source str

Python source code

required

Returns:

Name Type Description
return_type list[str]

List of scanned source code

Source code in cdd/shared/cst_utils.py
def cst_scanner(source):
    """
    Reduce source code into chunks useful for parsing.
    These chunks include newlines and the array is one dimensional.

    :param source: Python source code
    :type source: ```str```

    :return: List of scanned source code
    :rtype: ```list[str]```
    """
    scanned, stack = [], []
    for idx, ch in enumerate(source):
        if ch == "\n":  # in frozenset(("\n", ":", ";", '"""', "'''", '#')):
            cst_scan(scanned, stack)
        stack.append(ch)
    cst_scan(scanned, stack)
    if stack:
        scanned.append("".join(stack))
    return scanned

get_construct_name

get_construct_name(words)

Find the construct name, currently works for: - AsyncFunctionDef - FunctionDef - ClassDef

Parameters:

Name Type Description Default
words tuple[str]

Tuple of words (no whitespace)

required

Returns:

Name Type Description
return_type Optional[str]

Name of construct if found else None

Source code in cdd/shared/cst_utils.py
def get_construct_name(words):
    """
    Find the construct name, currently works for:
    - AsyncFunctionDef
    - FunctionDef
    - ClassDef

    :param words: Tuple of words (no whitespace)
    :type words: ```tuple[str]```

    :return: Name of construct if found else None
    :rtype: ```Optional[str]```
    """
    for idx, word in enumerate(words):
        if len(words) > idx + 1:
            if word == "def":
                return words[idx + 1][: words[idx + 1].find("(")]
            elif word == "class":
                end_idx = (
                    lambda _end_idx: (
                        words[idx + 1].find(":") if _end_idx == -1 else _end_idx
                    )
                )(words[idx + 1].find("("))
                return words[idx + 1][:end_idx]

infer_cst_type

infer_cst_type(statement_stripped, words)

Infer the CST type. This is the most important function of the CST parser!

Parameters:

Name Type Description Default
statement_stripped str

Original scanned statement minus both ends of whitespace

required
words Iterable[str]

List of whitespace stripped and empty-str removed words from original statement

required

Returns:

Name Type Description
return_type NamedTuple

CST type… a NamedTuple with at least ("line_no_start", "line_no_end", "value") attributes

Source code in cdd/shared/cst_utils.py
def infer_cst_type(statement_stripped, words):
    """
    Infer the CST type. This is the most important function of the CST parser!

    :param statement_stripped: Original scanned statement minus both ends of whitespace
    :type statement_stripped: ```str```

    :param words: List of whitespace stripped and empty-str removed words from original statement
    :type words: ```Iterable[str]```

    :return: CST type… a NamedTuple with at least ("line_no_start", "line_no_end", "value") attributes
    :rtype: ```NamedTuple```
    """
    if statement_stripped.startswith("["):
        return ListCompStatement
    elif statement_stripped.startswith("{"):
        # Won't work with nested dict inside a SetExpr
        if ":" in statement_stripped:
            return DictExprStatement
        return SetExprStatement
    elif statement_stripped.startswith("("):
        return GenExprStatement

    for word in words:
        if word in contains2statement:
            return contains2statement[word]

    if any(aug_assign in statement_stripped for aug_assign in augassign):
        return AugAssignment

    statement_frozenset = frozenset(statement_stripped)
    for key, constructor in multicontains2statement:
        if statement_frozenset & key == key:
            return constructor

    if any(math_operator in statement_stripped for math_operator in math_operators):
        return ExprStatement

    elif "(" in statement_frozenset:
        return CallStatement

    return UnchangingLine  # or: raise NotImplementedError(repr(statement_stripped))

reindent_block_with_pass_body

reindent_block_with_pass_body(s)

Reindent block (e.g., function definition) and give it a pass body

Parameters:

Name Type Description Default
s str

Block defining string

required

Returns:

Name Type Description
return_type str

Reindented string with pass body

Source code in cdd/shared/cst_utils.py
def reindent_block_with_pass_body(s):
    """
    Reindent block (e.g., function definition) and give it a `pass` body

    :param s: Block defining string
    :type s: ```str```

    :return: Reindented string with `pass` body
    :rtype: ```str```
    """
    return "{block_def} pass".format(
        block_def="\n".join(
            map(
                str.lstrip,
                s.split("\n"),
            )
        ).replace(tab, "", 1)
    )

set_prev_node

set_prev_node(function)

Store the result of the current function run into the prev_node attribute

Parameters:

Name Type Description Default
function Callable[[str, dict], NamedTuple]

The cst_parse_one_node function

required

Returns:

Name Type Description
return_type Callable[[], Callable[[str, dict], NamedTuple]]

Wrapped function

Source code in cdd/shared/cst_utils.py
def set_prev_node(function):
    """
    Store the result of the current function run into the `prev_node` attribute

    :param function: The `cst_parse_one_node` function
    :type function: ```Callable[[str, dict], NamedTuple]```

    :return: Wrapped function
    :rtype: ```Callable[[], Callable[[str, dict], NamedTuple]]```
    """

    @wraps(function)
    def wrapper(statement, state):
        """
        Store the result of parsing one statement into a CST node in the `prev_node` key of the `state` dict

        :param statement: Statement that was scanned from Python source code
        :type statement: ```str```

        :param state: Number of lines between runs in `acc` key; `prev_node`
        :type state: ```dict```

        :return: NamedTuple with at least ("line_no_start", "line_no_end", "value") attributes
        :rtype: ```NamedTuple```
        """
        state["prev_node"] = function(statement, state)
        state["parsed"].append(state["prev_node"])
        return state["prev_node"]

    return wrapper

cdd.shared.defaults_utils

cdd.shared.defaults_utils

Functions to handle default parameterisation

Parameters:

Name Type Description Default

ast_parse_fix

ast_parse_fix(s)

Hack to resolve unbalanced parentheses SyntaxError acquired from PyTorch parsing TODO: remove

Parameters:

Name Type Description Default
s str

String to parse

required

Returns:

Name Type Description
return_type

Value

Source code in cdd/shared/defaults_utils.py
def ast_parse_fix(s):
    """
    Hack to resolve unbalanced parentheses SyntaxError acquired from PyTorch parsing
    TODO: remove

    :param s: String to parse
    :type s: ```str```

    :return: Value
    """
    # return ast.parse(s).body[0].value
    balanced = (s.count("[") + s.count("]")) & 1 == 0
    return ast.parse(s if balanced else "{}]".format(s)).body[0].value

extract_default

extract_default(line, rstrip_default=True, default_search_announce=None, typ=None, emit_default_doc=True)

Extract a tuple of (doc, default) from a doc line

Parameters:

Name Type Description Default
line str

Example - "dataset. Defaults to mnist"

mnist
rstrip_default bool

Whether to rstrip whitespace, newlines, and '.' from the default

required
default_search_announce Optional[Union[str, Iterable[str]]]

Default text(s) to look for. If None, uses default specified in default_utils.

required
typ float

The type of the default value, useful to disambiguate 25 the float from 25 the float

required
emit_default_doc bool

Whether help/docstring should include 'With default' text

required

Returns:

Name Type Description
return_type tuple[str, Optional[str]]

Example - ("dataset. Defaults to mnist", "mnist") if emit_default_doc else ("dataset", "mnist")

Source code in cdd/shared/defaults_utils.py
def extract_default(
    line,
    rstrip_default=True,
    default_search_announce=None,
    typ=None,
    emit_default_doc=True,
):
    """
    Extract a tuple of (doc, default) from a doc line

    :param line: Example - "dataset. Defaults to mnist"
    :type line: ```str```

    :param rstrip_default: Whether to rstrip whitespace, newlines, and '.' from the default
    :type rstrip_default: ```bool```

    :param default_search_announce: Default text(s) to look for. If None, uses default specified in default_utils.
    :type default_search_announce: ```Optional[Union[str, Iterable[str]]]```

    :param typ: The type of the default value, useful to disambiguate `25` the float from  `25` the float
    :type typ: ```Optional[str]```

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

    :return: Example - ("dataset. Defaults to mnist", "mnist") if emit_default_doc else ("dataset", "mnist")
    :rtype: ```tuple[str, Optional[str]]```
    """
    if line is None:
        return line, line

    default_search_announce_paren, default_search_announce = (
        lambda _default_search_announce: (
            map(partial(str.format, "({}"), _default_search_announce),
            _default_search_announce,
        )
    )(
        DEFAULTS_TO_VARIANTS
        if default_search_announce is None
        else (
            (default_search_announce,)
            if isinstance(default_search_announce, str)
            else default_search_announce
        )
    )

    _start_idx, _end_idx, default_end_offset = None, None, None
    for idx, _default_search_announce in enumerate(
        (default_search_announce_paren, default_search_announce)
    ):
        _start_idx, _end_idx, _found = location_within(
            line,
            _default_search_announce,
            cmp=lambda a, b: eq(*map(str.casefold, (a, b))),
        )

        if idx == 0:
            if _start_idx != -1:
                _start_idx += 1  # eat '('
                default_end_offset = (
                    -1 if line[-1] == ")" else -2 if line[-2:] == ")." else 0
                )  # eat ')', ').'
                break
        elif _start_idx < 0:
            return line, None

    default = ""
    par: Dict[str, int] = {"{": 0, "[": 0, "(": 0, ")": 0, "]": 0, "}": 0}
    sub_l: str = line[_end_idx:default_end_offset]
    sub_l_len: int = len(sub_l)
    for idx, ch in enumerate(sub_l):
        if (
            ch == "."
            and (idx == (sub_l_len - 1) or not (sub_l[idx + 1]).isdigit())
            and not sum(par.values())
        ):
            break
        elif ch in par:
            par[ch] += 1
        default += ch

    start_rest_offset = _end_idx + len(default)

    default = default.strip(" \t`")

    if default.count('"') & 1:
        default = default.strip('"')
    if default.count("'") & 1:
        default = default.strip("'")

    # Correct for parsing errors where a quote is captured at the start or end, but not both
    if len(default) > 0:
        if default.count('"') == 1 and default.count("'") == 0:
            if default.startswith('"'):
                default = default + '"'
            elif default.endswith('"'):
                default = '"' + default
        elif default.count("'") == 1 and default.count('"') == 0:
            if default.startswith("'"):
                default = default + "'"
            elif default.endswith("'"):
                default = "'" + default

    return _parse_out_default_and_doc(
        _start_idx,
        start_rest_offset,
        default,
        line,
        rstrip_default,
        typ,
        default_end_offset,
        emit_default_doc,
    )

needs_quoting

needs_quoting(typ)

Figures out whether values with this type need quoting

Parameters:

Name Type Description Default
typ Optional[str]

The type

required

Returns:

Name Type Description
return_type bool

Whether the type needs quoting

Source code in cdd/shared/defaults_utils.py
def needs_quoting(typ):
    """
    Figures out whether values with this type need quoting

    :param typ: The type
    :type typ: ```Optional[str]```

    :return: Whether the type needs quoting
    :rtype: ```bool```
    """
    if typ is None or typ.startswith("*"):
        return False
    elif typ == "str":
        return True
    elif typ == "Optional[str]":
        return True

    typ = typ.replace("\n", "").strip()
    parsed_typ_ast = ast_parse_fix(typ)
    if isinstance(parsed_typ_ast, ast.Name):
        return parsed_typ_ast.id == "str"

    return any(
        filter(
            lambda node: isinstance(node, Str)
            or isinstance(node, ast.Constant)
            and type(node.value).__name__ == "str"
            or isinstance(node, ast.Name)
            and node.id == "str",
            ast.walk(parsed_typ_ast),
        )
    )

remove_defaults_from_intermediate_repr

remove_defaults_from_intermediate_repr(intermediate_repr, emit_default_prop=True)

Remove "Default of" text from IR

Parameters:

Name Type Description Default
intermediate_repr dict

a dictionary consistent with IntermediateRepr, defined as: ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any}) IntermediateRepr = TypedDict("IntermediateRepr", { "name": Optional[str], "type": Optional[str], "doc": Optional[str], "params": OrderedDict[str, ParamVal], "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]], })

required
emit_default_prop bool

Whether to emit default property

required

Returns:

Name Type Description
return_type dict

a dictionary consistent with IntermediateRepr, defined as: ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any}) IntermediateRepr = TypedDict("IntermediateRepr", { "name": Optional[str], "type": Optional[str], "doc": Optional[str], "params": OrderedDict[str, ParamVal], "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]], })

Source code in cdd/shared/defaults_utils.py
def remove_defaults_from_intermediate_repr(intermediate_repr, emit_default_prop=True):
    """
    Remove "Default of" text from IR

    :param intermediate_repr: a dictionary consistent with `IntermediateRepr`, defined as:
        ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any})
        IntermediateRepr = TypedDict("IntermediateRepr", {
            "name": Optional[str],
            "type": Optional[str],
            "doc": Optional[str],
            "params": OrderedDict[str, ParamVal],
            "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]],
        })
    :type intermediate_repr: ```dict```

    :param emit_default_prop: Whether to emit default property
    :type emit_default_prop: ```bool```

    :return: a dictionary consistent with `IntermediateRepr`, defined as:
        ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any})
        IntermediateRepr = TypedDict("IntermediateRepr", {
            "name": Optional[str],
            "type": Optional[str],
            "doc": Optional[str],
            "params": OrderedDict[str, ParamVal],
            "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]],
        })
    :rtype: ```dict```
    """
    ir: IntermediateRepr = deepcopy(intermediate_repr)

    remove_default_from_param = partial(
        _remove_default_from_param, emit_default_prop=emit_default_prop
    )
    ir.update(
        {
            key: OrderedDict(map(remove_default_from_param, ir[key].items()))
            for key in ("params", "returns")
        }
    )
    return ir

set_default_doc

set_default_doc(param, emit_default_doc=True)

Emit param with 'doc' set to include 'Defaults'

Parameters:

Name Type Description Default
param dict

Name, dict with keys: 'typ', 'doc', 'default'

required
emit_default_doc bool

Whether help/docstring should include 'With default' text

required

Returns:

Name Type Description
return_type dict

Same shape as input but with Default append to doc.

Source code in cdd/shared/defaults_utils.py
def set_default_doc(param, emit_default_doc=True):
    """
    Emit param with 'doc' set to include 'Defaults'

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

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

    :return: Same shape as input but with Default append to doc.
    :rtype: ```dict```
    """
    name, _param = param
    del param
    # if param is None: param = {"doc": "", "typ": "Any"}
    if _param is None or "doc" not in _param:
        return name, _param
    has_defaults = "Defaults" in _param["doc"] or "defaults" in _param["doc"]

    if has_defaults and not emit_default_doc:
        # Remove the default text
        _param["doc"] = extract_default(
            _param["doc"], emit_default_doc=emit_default_doc
        )[0]
    elif "default" in _param and not has_defaults and emit_default_doc:
        # if _param["default"] == NoneStr: _param["default"] = None
        if _param["default"] is not None or not name.endswith("kwargs"):
            _param["doc"] = "{doc} Defaults to {default}".format(
                doc=(
                    _param["doc"]
                    if _param["doc"][-1] in frozenset((".", ","))
                    else "{doc}.".format(doc=_param["doc"])
                ),
                default=(
                    quote(_param["default"])
                    if (
                        needs_quoting(_param.get("typ"))
                        and (
                            len(_param["default"]) < 2
                            or not _param["default"].startswith("`")
                            or not _param["default"].endswith("`")
                        )
                        if isinstance(_param["default"], str)
                        else True
                    )
                    else _param["default"]
                ),
            )

    return name, _param

cdd.shared.docstring_parsers

cdd.shared.docstring_parsers

Docstring parsers.

Parses these formats into the cdd_python common IR format: - ReST docstring format (Sphinx) - numpydoc docstring format - Google's docstring format

Parameters:

Name Type Description Default

Style

Bases: Enum

Simple enum taken from the docstring_parser codebase

Parameters:

Name Type Description Default

__set_name_and_type_handle_doc_in_param

__set_name_and_type_handle_doc_in_param(_param, name, was_none, word_wrap)

Internal function to internal function _set_name_and_type.

Parameters:

Name Type Description Default
_param dict

dict with keys: 'typ', 'doc', 'default'

required
name str

Name of argument; useful for debugging and if the name hints as to the type

required
was_none bool

Whether the default value was None

required
word_wrap bool

Whether to word-wrap. Set DOCTRANS_LINE_LENGTH to configure length.

required
Source code in cdd/shared/docstring_parsers.py
def __set_name_and_type_handle_doc_in_param(_param, name, was_none, word_wrap):
    """
    Internal function to internal function `_set_name_and_type`.

    :param _param: dict with keys: 'typ', 'doc', 'default'
    :type _param: ```dict```

    :param name: Name of argument; useful for debugging and if the name hints as to the type
    :type name: ```str```

    :param was_none: Whether the default value was `None`
    :type was_none: ```bool```

    :param word_wrap: Whether to word-wrap. Set `DOCTRANS_LINE_LENGTH` to configure length.
    :type word_wrap: ```bool```
    """
    if "doc" in _param:
        if not isinstance(_param["doc"], str):
            _param["doc"] = "".join(_param["doc"]).rstrip()
        else:
            _param["doc"] = (
                " ".join(map(str.strip, _param["doc"].split("\n")))
                if word_wrap
                else _param["doc"]
            ).rstrip()

        typ = parse_adhoc_doc_for_typ(
            _param["doc"], name, _param.get("default") == cdd.shared.ast_utils.NoneStr
        )
        if typ is not None:
            try:
                eval(typ, globals(), locals())
                _param["typ"] = typ
            except (NameError, SyntaxError, TypeError) as e:
                print(e, file=sys.stderr)

        if (
            (_param["doc"].startswith(("(Optional)", "Optional")) or was_none)
            and "typ" in _param
            and not _param["typ"].startswith("Optional[")
        ):
            _param["typ"] = "Optional[{typ}]".format(typ=_param["typ"])

parse_docstring

parse_docstring(docstring, infer_type=False, default_search_announce=None, parse_original_whitespace=False, word_wrap=True, emit_default_prop=True, emit_default_doc=False)

Parse the docstring into its components.

Parameters:

Name Type Description Default
docstring Optional[str]

the docstring

required
default_search_announce Optional[Union[str, Iterable[str]]]

Default text(s) to look for. If None, uses default specified in default_utils.

required
infer_type bool

Whether to try inferring the typ (from the default)

required
parse_original_whitespace bool

Whether to parse original whitespace or strip it out

required
word_wrap bool

Whether to word-wrap. Set DOCTRANS_LINE_LENGTH to configure length.

required
emit_default_prop dict

Whether to include the default dictionary property.

required
emit_default_doc bool

Whether help/docstring should include 'With default' text

required

Returns:

Name Type Description
return_type dict

a dictionary consistent with IntermediateRepr, defined as: ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any}) IntermediateRepr = TypedDict("IntermediateRepr", { "name": Optional[str], "type": Optional[str], "doc": Optional[str], "params": OrderedDict[str, ParamVal], "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]], })

Source code in cdd/shared/docstring_parsers.py
def parse_docstring(
    docstring,
    infer_type=False,
    default_search_announce=None,
    parse_original_whitespace=False,
    word_wrap=True,
    emit_default_prop=True,
    emit_default_doc=False,
):
    """Parse the docstring into its components.

    :param docstring: the docstring
    :type docstring: ```Optional[str]```

    :param default_search_announce: Default text(s) to look for. If None, uses default specified in default_utils.
    :type default_search_announce: ```Optional[Union[str, Iterable[str]]]```

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

    :param parse_original_whitespace: Whether to parse original whitespace or strip it out
    :type parse_original_whitespace: ```bool```

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

    :param emit_default_prop: Whether to include the default dictionary property.
    :type emit_default_prop: ```bool```

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

    :return: a dictionary consistent with `IntermediateRepr`, defined as:
        ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any})
        IntermediateRepr = TypedDict("IntermediateRepr", {
            "name": Optional[str],
            "type": Optional[str],
            "doc": Optional[str],
            "params": OrderedDict[str, ParamVal],
            "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]],
        })
    :rtype: ```dict```
    """

    assert isinstance(
        docstring, (type(None), str)
    ), "Expected `Union[type(None), str]` got `{type_name}`".format(
        type_name=type(docstring).__name__
    )
    style: Style = derive_docstring_format(docstring)

    ir: IntermediateRepr = {
        "name": None,
        "type": "static",
        # "_internal": {"_style": style.value},
        "doc": "",
        "params": OrderedDict(),
        "returns": None,
    }
    if not docstring:
        return ir

    scanned = _scan_phase(docstring, style=style)

    _parse_phase(
        ir,
        scanned,
        default_search_announce=default_search_announce,
        emit_default_doc=emit_default_doc,
        emit_default_prop=emit_default_prop,
        infer_type=infer_type,
        parse_original_whitespace=parse_original_whitespace,
        style=style,
        word_wrap=word_wrap,
    )

    # Apply certain functions regardless of style
    if style is Style.rest:
        ir.update(
            {
                k: (
                    OrderedDict(
                        map(
                            partial(
                                interpolate_defaults,
                                emit_default_doc=emit_default_doc,
                                require_default=False,
                            ),
                            ir[k].items(),
                        )
                    )
                    if ir[k]
                    else ir[k]
                )
                for k in ("params", "returns")
            }
        )
    return ir

cdd.shared.docstring_utils

cdd.shared.docstring_utils

Functions which produce docstring portions from various inputs

Parameters:

Name Type Description Default

Style

Bases: Enum

Simple enum taken from the docstring_parser codebase

Parameters:

Name Type Description Default

derive_docstring_format

derive_docstring_format(docstring)

Infer the docstring format of the provided docstring

Parameters:

Name Type Description Default
docstring Optional[str]

the docstring

required

Returns:

Name Type Description
return_type Literal['rest', 'numpydoc', 'google']

the style of docstring

Source code in cdd/shared/docstring_utils.py
def derive_docstring_format(docstring):
    """
    Infer the docstring format of the provided docstring

    :param docstring: the docstring
    :type docstring: ```Optional[str]```

    :return: the style of docstring
    :rtype: ```Literal['rest', 'numpydoc', 'google']```
    """
    if docstring is None or any(map(partial(contains, docstring), TOKENS.rest)):
        style = Style.rest
    elif any(map(partial(contains, docstring), TOKENS.google)):
        style = Style.google
    else:
        style = Style.numpydoc
    return style

emit_param_str

emit_param_str(param, style, purpose, emit_doc=True, emit_type=True, word_wrap=True, emit_default_doc=True)

Produce the docstring param/return lines

Parameters:

Name Type Description Default
param dict

Name, dict with keys: 'typ', 'doc', 'default'

required
style Literal['rest', 'numpydoc', 'google']

the style of docstring

required
purpose Literal['class', 'function']
required
if purpose == 'function' elif purpose == 'class' then

:paramif purpose == 'function' elif purpose == 'class' then

required
(ReST only)

:cvar` (ReST only)

required
emit_doc bool

Whether to emit the doc

required
emit_type bool

Whether to emit the type

required
word_wrap bool

Whether to word-wrap. Set DOCTRANS_LINE_LENGTH to configure length.

required
emit_default_doc bool

Whether help/docstring should include 'With default' text

required

Returns:

Name Type Description
return_type str

Newline joined pair of param, type

Source code in cdd/shared/docstring_utils.py
def emit_param_str(
    param,
    style,
    purpose,
    emit_doc=True,
    emit_type=True,
    word_wrap=True,
    emit_default_doc=True,
):
    """
    Produce the docstring param/return lines

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

    :param style: the style of docstring
    :type style: ```Literal['rest', 'numpydoc', 'google']```

    :param purpose: Emit `:param` if purpose == 'function' elif purpose == 'class' then `:cvar` (ReST only)
    :type purpose: ```Literal['class', 'function']```

    :param emit_doc: Whether to emit the doc
    :type emit_doc: ```bool```

    :param emit_type: Whether to emit the type
    :type emit_type: ```bool```

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

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

    :return: Newline joined pair of param, type
    :rtype: ```str```
    """
    name, _param = param
    del param

    _fill = fill if word_wrap else identity

    if style == "rest":
        emit_type &= purpose == "function"
        key, key_typ = (
            ("return", "rtype")
            if name == "return_type" and purpose != "class"
            else (
                "{var} {name}".format(
                    var="param" if purpose == "function" else "cvar", name=name
                ),
                "type {name}".format(name=name),
            )
        )

        return "\n".join(
            map(
                indent_all_but_first,
                map(
                    _fill,
                    filter(
                        None,
                        (
                            (
                                ":{key}: {doc}".format(
                                    key=key,
                                    doc=set_default_doc(
                                        (name, _param),
                                        emit_default_doc=emit_default_doc,
                                    )[1]["doc"].lstrip(),
                                )
                                if emit_doc and _param.get("doc")
                                else None
                            ),
                            (
                                ":{key_typ}: ```{typ}```".format(
                                    key_typ=key_typ, typ=_param["typ"]
                                )
                                if emit_type and _param.get("typ")
                                else None
                            ),
                        ),
                    ),
                ),
            )
        )
    elif style == "numpydoc":
        return "\n".join(
            filter(
                None,
                (
                    (
                        _fill(
                            (_param["typ"] if _param.get("typ") else None)
                            if name == "return_type"
                            else "{name} :{typ}".format(
                                name=name,
                                typ=(
                                    " {typ}".format(typ=_param["typ"])
                                    if _param.get("typ")
                                    else ""
                                ),
                            )
                        )
                        if emit_type and _param.get("typ")
                        else None
                    ),
                    (
                        _fill(
                            indent(
                                set_default_doc(
                                    (name, _param), emit_default_doc=emit_default_doc
                                )[1]["doc"],
                                tab,
                            )
                        )
                        if emit_doc and _param.get("doc")
                        else None
                    ),
                ),
            )
        )
    else:
        return "".join(
            filter(
                None,
                (
                    (
                        (
                            "  {typ}:".format(typ=_param["typ"])
                            if _param.get("typ")
                            else None
                        )
                        if name == "return_type"
                        else (
                            "  {name} ({typ}): ".format(
                                name=name,
                                typ=(
                                    "{typ!s}".format(typ=_param["typ"])
                                    if _param.get("typ")
                                    else ""
                                ),
                            )
                            if _param.get("typ")
                            else "  {name}: ".format(name=name)
                        )
                    ),
                    (
                        "{nl}{tab}{doc}".format(
                            doc=set_default_doc(
                                (name, _param), emit_default_doc=emit_default_doc
                            )[1]["doc"],
                            **(
                                {"nl": "\n", "tab": " " * 3}
                                if name == "return_type"
                                else {"nl": "", "tab": ""}
                            )
                        )
                        if emit_doc and _param.get("doc")
                        else None
                    ),
                ),
            )
        )

ensure_doc_args_whence_original

ensure_doc_args_whence_original(current_doc_str, original_doc_str)

Ensure doc args appear where they appeared originally

Parameters:

Name Type Description Default
current_doc_str str

The current doc_str

required
original_doc_str str

The original doc_str

required

Returns:

Name Type Description
return_type str

reshuffled doc_str with args/returns in same place as original (same header, footer, and whitespace)

Source code in cdd/shared/docstring_utils.py
def ensure_doc_args_whence_original(current_doc_str, original_doc_str):
    """
    Ensure doc args appear where they appeared originally

    :param current_doc_str: The current doc_str
    :type current_doc_str: ```str```

    :param original_doc_str: The original doc_str
    :type original_doc_str: ```str```

    :return: reshuffled doc_str with args/returns in same place as original (same header, footer, and whitespace)
    :rtype: ```str```
    """
    if original_doc_str and eq(
        *map(omit_whitespace, (original_doc_str, current_doc_str))
    ):
        return original_doc_str
    (
        original_header,
        current_args_returns,
        original_footer,
    ) = parse_docstring_into_header_args_footer(current_doc_str, original_doc_str)

    return header_args_footer_to_str(
        header=original_header or "",
        args_returns=current_args_returns or "",
        footer=original_footer or "",
    )
header_args_footer_to_str(header, args_returns, footer)

Ensure there is always two newlines between header and next, and one between non-empty: args_returns and footer

Parameters:

Name Type Description Default
header str

Header section

required
args_returns str

args|returns section

required
footer str

Footer section

required

Returns:

Name Type Description
return_type str

One string with these three section combined with a minimum of one nl betwixt each and two at first

Source code in cdd/shared/docstring_utils.py
def header_args_footer_to_str(header, args_returns, footer):
    """
    Ensure there is always two newlines between header and next, and one between non-empty: args_returns and footer

    :param header: Header section
    :type header: ```str```

    :param args_returns: args|returns section
    :type args_returns: ```str```

    :param footer: Footer section
    :type footer: ```str```

    :return: One string with these three section combined with a minimum of one nl betwixt each and two at first
    :rtype: ```str```
    """
    header_end_nls = num_of_nls(header, end=True) if header else 0
    if args_returns:
        args_returns_start_nls = num_of_nls(args_returns, end=False)
        args_returns_ends_nls = num_of_nls(args_returns, end=True)
        args_returns = "{maybe_nls0}{args_returns}{maybe_nl1}".format(
            maybe_nls0=(
                "\n" * (args_returns_start_nls or 2)
                if args_returns_start_nls < 2 and header and not header_end_nls
                else ""
            ),
            args_returns=args_returns,
            maybe_nl1="\n" if not args_returns_ends_nls else "",
        )
        args_returns_start_nls = num_of_nls(args_returns, end=False)
        args_returns_ends_nls = num_of_nls(args_returns, end=True)
    else:
        args_returns_start_nls = args_returns_ends_nls = 0
    if footer:
        footer_start_nls = (
            count_chars_from(footer, str.isspace, "\n", end=False)
            or args_returns_ends_nls
        )
        # foot_end_has_nl = footer[-1] == "\n"
    else:
        footer_start_nls: int = 0  # foot_end_has_nl

    # Match indent of args_returns to header or footer
    if args_returns:
        header_or_footer: str = header if header else footer
        indent_amount: int = count_iter_items(takewhile(str.isspace, header_or_footer))
        newlines: int = (
            header_or_footer[:indent_amount].count("\n") if header_or_footer else 0
        )
        indent_amount: int = indent_amount - newlines
        current_indent_amount: int = count_iter_items(
            takewhile(str.isspace, args_returns)
        )
        if current_indent_amount != indent_amount:
            _indent: str = indent_amount * " "
            len_args_returns: int = len(args_returns)
            args_returns: str = indent(args_returns, _indent, predicate=lambda _: _)
            if args_returns[-1] == "\n" and len_args_returns > 1:
                args_returns += _indent

    nls_after_header = header_end_nls + args_returns_start_nls
    nls_needed_after_header = (
        0
        if (nls_after_header > 1 or not header or not args_returns)
        else 1 if nls_after_header == 1 else 2 if nls_after_header == 0 else 0
    )

    return "{header}{maybe_nl0}{args_returns}{maybe_nl1}{footer}{maybe_nl2}".format(
        header=header,
        maybe_nl0="\n" * nls_needed_after_header,
        args_returns=args_returns,
        maybe_nl1=(
            "\n"
            if args_returns
            and footer
            and not footer_start_nls
            and not args_returns_ends_nls
            else ""
        ),
        footer=footer,
        maybe_nl2="",  # if foot_end_has_nl else "\n",
    )
parse_docstring_into_header_args_footer(current_doc_str, original_doc_str)

Parse docstring into three parts: header; args|returns; footer

Parameters:

Name Type Description Default
current_doc_str str

The current doc_str

required
original_doc_str str

The original doc_str

required

Returns:

Name Type Description
return_type tuple[Optional[str], Optional[str], Optional[str]]

Header, args|returns, footer

Source code in cdd/shared/docstring_utils.py
def parse_docstring_into_header_args_footer(current_doc_str, original_doc_str):
    """
    Parse docstring into three parts: header; args|returns; footer

    :param current_doc_str: The current doc_str
    :type current_doc_str: ```str```

    :param original_doc_str: The original doc_str
    :type original_doc_str: ```str```

    :return: Header, args|returns, footer
    :rtype: ```tuple[Optional[str], Optional[str], Optional[str]]```
    """
    # if not current_doc_str and not original_doc_str: return None, None, None

    # To quieten linter
    header_original = footer_original = None
    start_idx_current = last_idx_current = start_idx_original = last_idx_original = None

    if current_doc_str:
        start_idx_current = _get_token_start_idx(current_doc_str)
        last_idx_current = _get_token_last_idx(current_doc_str)
        # footer_current = (
        #     current_doc_str[last_idx_current:] if last_idx_current != -1 else None
        # )
        # header_current = (
        #     original_doc_str[:start_idx_current] if start_idx_current > -1 else None
        # )
        #
        # args_returns_current = current_doc_str[
        #     slice(
        #         start_idx_current if start_idx_current > -1 else None,
        #         last_idx_current if last_idx_current > -1 else None,
        #     )
        # ]

    if original_doc_str:
        start_idx_original = _get_token_start_idx(original_doc_str)
        last_idx_original = _get_token_last_idx(original_doc_str)
        header_original = (
            original_doc_str[:start_idx_original] if start_idx_original > -1 else None
        )
        args_returns_original = original_doc_str[
            slice(
                start_idx_original if start_idx_original > -1 else None,
                last_idx_original if last_idx_original > -1 else None,
            )
        ]
        footer_original = (
            original_doc_str[last_idx_original:] if last_idx_original != -1 else None
        )

    # Now we know where the args/returns were, and where they are now
    # To avoid whitespace issues, only copy across the args/returns portion, keep rest as original

    args_returns_current, args_returns_original = map(
        lambda doc_start_end: doc_start_end[0][
            slice(
                (
                    doc_start_end[1]
                    if doc_start_end[1] is not None and doc_start_end[1] > -1
                    else None
                ),
                (
                    doc_start_end[2]
                    if doc_start_end[2] is not None and doc_start_end[2] > -1
                    else None
                ),
            )
        ],
        (
            (current_doc_str, start_idx_current, last_idx_current),
            (original_doc_str, start_idx_original, last_idx_original),
        ),
    )

    indent_args_returns_original: int = count_iter_items(
        takewhile(str.isspace, args_returns_original or iter(()))
    )
    if indent_args_returns_original > 1 and args_returns_current:
        args_returns_current = indent(
            args_returns_current,
            prefix=" " * indent_args_returns_original,
            predicate=lambda _: _,
        )

    return header_original, args_returns_current, footer_original

cdd.shared.emit

cdd.shared.emit

Transform from string or AST representations of input, to AST, file, or str input_str.

Parameters:

Name Type Description Default

cdd.shared.emit.file

cdd.shared.emit.file

File emitter

Parameters:

Name Type Description Default

file

file(node, filename, mode='a', skip_black=False)

Convert AST to a file

Parameters:

Name Type Description Default
node Union[Module, ClassDef, FunctionDef]

AST node

required
filename str

emit to this file

required
mode str

Mode to open the file in, defaults to append

append
skip_black bool

Whether to skip formatting with black

required

Returns:

Name Type Description
return_type NoneType

None

Source code in cdd/shared/emit/file.py
def file(node, filename, mode="a", skip_black=False):
    """
    Convert AST to a file

    :param node: AST node
    :type node: ```Union[Module, ClassDef, FunctionDef]```

    :param filename: emit to this file
    :type filename: ```str```

    :param mode: Mode to open the file in, defaults to append
    :type mode: ```str```

    :param skip_black: Whether to skip formatting with black
    :type skip_black: ```bool```

    :return: None
    :rtype: ```NoneType```
    """
    if not isinstance(node, Module):
        node: Module = Module(body=[node], type_ignores=[], stmt=None)
    src: str = cdd.shared.source_transformer.to_code(node)
    if not skip_black:
        src = black.format_str(
            src,
            mode=black.Mode(
                target_versions=set(),
                line_length=119,
                is_pyi=False,
                string_normalization=False,
            ),
        )
    with open(filename, mode) as f:
        f.write(src)

cdd.shared.emit.utils

cdd.shared.emit.utils

Utility functions for cdd.emit

Parameters:

Name Type Description Default

cdd.shared.emit.utils.emitter_utils

cdd.shared.emit.utils.emitter_utils

Functions which produce intermediate_repr from various different inputs

Parameters:

Name Type Description Default

ast_parse_fix

ast_parse_fix(s)

Hack to resolve unbalanced parentheses SyntaxError acquired from PyTorch parsing TODO: remove

Parameters:

Name Type Description Default
s str

String to parse

required

Returns:

Name Type Description
return_type

Value

Source code in cdd/shared/emit/utils/emitter_utils.py
def ast_parse_fix(s):
    """
    Hack to resolve unbalanced parentheses SyntaxError acquired from PyTorch parsing
    TODO: remove

    :param s: String to parse
    :type s: ```str```

    :return: Value
    """
    balanced: bool = (s.count("[") + s.count("]")) & 1 == 0
    return ast.parse(s if balanced else "{}]".format(s)).body[0].value

get_emitter

get_emitter(emit_name)

Get emitter function specialised for output node

Parameters:

Name Type Description Default
emit_name Literal["argparse", "class", "function", "json_schema", "pydantic", "sqlalchemy", "sqlalchemy_table", "sqlalchemy_hybrid"]

Which type to emit.

required

Returns:

Name Type Description
return_type Callable[[...], dict]`

Function which returns intermediate_repr

Source code in cdd/shared/emit/utils/emitter_utils.py
def get_emitter(emit_name):
    """
    Get emitter function specialised for output `node`

    :param emit_name: Which type to emit.
    :type emit_name: ```Literal["argparse", "class", "function", "json_schema",
                                 "pydantic", "sqlalchemy", "sqlalchemy_table", "sqlalchemy_hybrid"]```

    :return: Function which returns intermediate_repr
    :rtype: ```Callable[[...], dict]````
    """
    emit_name: str = {"class": "class_"}.get(emit_name, emit_name)
    return getattr(
        import_module(
            ".".join(
                (
                    "cdd",
                    (
                        "sqlalchemy"
                        if emit_name
                        in frozenset(("sqlalchemy_hybrid", "sqlalchemy_table"))
                        else emit_name
                    ),
                    "emit",
                )
            )
        ),
        emit_name,
    )

get_internal_body

get_internal_body(target_name, target_type, intermediate_repr)

Get the internal body from our IR

Parameters:

Name Type Description Default
target_name str

name of target. If both target_name and target_type match internal body extract, then emit

required
target_type Literal['self', 'cls', 'static']

Type of target, static is static or global method, others just become first arg

required
intermediate_repr dict

a dictionary consistent with IntermediateRepr, defined as: ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any}) IntermediateRepr = TypedDict("IntermediateRepr", { "name": Optional[str], "type": Optional[str], "doc": Optional[str], "params": OrderedDict[str, ParamVal], "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]], })

required

Returns:

Name Type Description
return_type Union[list, tuple]

Internal body or an empty tuple

Source code in cdd/shared/emit/utils/emitter_utils.py
def get_internal_body(target_name, target_type, intermediate_repr):
    """
    Get the internal body from our IR

    :param target_name: name of target. If both `target_name` and `target_type` match internal body extract, then emit
    :type target_name: ```str```

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

    :param intermediate_repr: a dictionary consistent with `IntermediateRepr`, defined as:
        ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any})
        IntermediateRepr = TypedDict("IntermediateRepr", {
            "name": Optional[str],
            "type": Optional[str],
            "doc": Optional[str],
            "params": OrderedDict[str, ParamVal],
            "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]],
        })
    :type intermediate_repr: ```dict```

    :return: Internal body or an empty tuple
    :rtype: ```Union[list, tuple]```
    """
    return (
        intermediate_repr["_internal"]["body"]
        if intermediate_repr.get("_internal", {}).get("body")
        and intermediate_repr["_internal"]["from_name"] == target_name
        and intermediate_repr["_internal"]["from_type"] == target_type
        else tuple()
    )

cdd.shared.parse

cdd.shared.parse

Transform from string or AST representations of input, to intermediate_repr, a dictionary consistent with IntermediateRepr, defined as: ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any}) IntermediateRepr = TypedDict("IntermediateRepr", { "name": Optional[str], "type": Optional[str], "doc": Optional[str], "params": OrderedDict[str, ParamVal], "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]], })

Parameters:

Name Type Description Default

cdd.shared.parse.utils

cdd.shared.parse.utils

Utility functions for cdd.parse

Parameters:

Name Type Description Default

cdd.shared.parse.utils.parser_utils

cdd.shared.parse.utils.parser_utils

Functions which help the functions within the parser module

Parameters:

Name Type Description Default

get_parser

get_parser(node, parse_name)

Get parser function specialised for input node

Parameters:

Name Type Description Default
node AST

Node to parse

required
parse_name Literal["argparse", "class", "function", "json_schema", "pydantic", "sqlalchemy", "sqlalchemy_table", "sqlalchemy_hybrid","infer"]

Which type to parse.

required

Returns:

Name Type Description
return_type Callable[[...], dict]`

Function which returns intermediate_repr

Source code in cdd/shared/parse/utils/parser_utils.py
def get_parser(node, parse_name):
    """
    Get parser function specialised for input `node`

    :param node: Node to parse
    :type node: ```AST```

    :param parse_name: Which type to parse.
    :type parse_name: ```Literal["argparse", "class", "function", "json_schema",
                                 "pydantic", "sqlalchemy", "sqlalchemy_table", "sqlalchemy_hybrid","infer"]```

    :return: Function which returns intermediate_repr
    :rtype: ```Callable[[...], dict]````
    """
    if parse_name in (None, "infer"):
        parse_name: str = infer(node)
    parse_name: str = {
        "class": "class_",
        "sqlalchemy_hybrid": "sqlalchemy",
        "sqlalchemy_table": "sqlalchemy",
    }.get(parse_name, parse_name)
    return getattr(import_module(".".join(("cdd", parse_name, "parse"))), parse_name)

infer

infer(*args, **kwargs)

Infer the parse type

Parameters:

Name Type Description Default
args tuple[args]

The arguments

required
kwargs Optional[dict]

Keyword arguments

required

Returns:

Name Type Description
return_type str

Name of inferred parser

Source code in cdd/shared/parse/utils/parser_utils.py
def infer(*args, **kwargs):
    """
    Infer the `parse` type

    :param args: The arguments
    :type args: ```tuple[args]```

    :param kwargs: Keyword arguments
    :type kwargs: ```dict```

    :return: Name of inferred parser
    :rtype: ```str```
    """
    node = (
        args[0]
        if args
        else kwargs.get(
            "class_def", kwargs.get("function_def", kwargs.get("call_or_name"))
        )
    )
    is_supported_ast_node: bool = isinstance(
        node, (Module, Assign, AnnAssign, Call, ClassDef, FunctionDef)
    )
    if not is_supported_ast_node and (
        isinstance(node, (type, FunctionType)) or type(node).__name__ == "function"
    ):
        return infer(ast.parse(getsource(node)).body[0])

    if not is_supported_ast_node:
        if not isinstance(node, str):
            node = cdd.shared.ast_utils.get_value(node)
        if (
            isinstance(node, str)
            and not node.startswith("def ")
            and not node.startswith("class ")
        ):
            return "docstring"

    if isinstance(node, FunctionDef):
        if next(
            filter(
                partial(eq, "argument_parser"), map(attrgetter("arg"), node.args.args)
            ),
            False,
        ):
            return "argparse_ast"

        return "function"

    elif isinstance(node, ClassDef):
        if any(
            filter(
                partial(eq, "Base"),
                map(attrgetter("id"), filter(rpartial(hasattr, "id"), node.bases)),
            )
        ):
            return "sqlalchemy"
        return "class_"
    elif isinstance(node, (AnnAssign, Assign)):
        return infer(node.value)
    elif isinstance(node, Call):
        if len(node.args) > 2 and node.args[1].id == "metadata":
            return "sqlalchemy_table"
    else:
        raise NotImplementedError(node)

ir_merge

ir_merge(target, other)

Merge two intermediate_repr (IR) together. It doesn't do a target.update(other), instead it carefully merges params and returns

Parameters:

Name Type Description Default
target str

The target IR to modify. These values take precedence. Dict is consistent with IntermediateRepr: ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any}) IntermediateRepr = TypedDict("IntermediateRepr", { "name": Optional[str], "type": Optional[str], "doc": Optional[str], "params": OrderedDict[str, ParamVal], "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]], })

required
other dict

Read-only IR to use in update. IR is a dictionary consistent with IntermediateRepr, defined as: ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any}) IntermediateRepr = TypedDict("IntermediateRepr", { "name": Optional[str], "type": Optional[str], "doc": Optional[str], "params": OrderedDict[str, ParamVal], "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]], })

required

Returns:

Name Type Description
return_type dict

IR of updated target. target is also updated in-place, and the memory of other is used.

Source code in cdd/shared/parse/utils/parser_utils.py
def ir_merge(target, other):
    """
    Merge two intermediate_repr (IR) together. It doesn't do a `target.update(other)`,
     instead it carefully merges `params` and `returns`

    :param target: The target IR to modify. These values take precedence. Dict is consistent with `IntermediateRepr`:
        ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any})
        IntermediateRepr = TypedDict("IntermediateRepr", {
            "name": Optional[str],
            "type": Optional[str],
            "doc": Optional[str],
            "params": OrderedDict[str, ParamVal],
            "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]],
        })
    :type target: ```dict```

    :param other: Read-only IR to use in update. IR is a dictionary consistent with `IntermediateRepr`, defined as:
        ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any})
        IntermediateRepr = TypedDict("IntermediateRepr", {
            "name": Optional[str],
            "type": Optional[str],
            "doc": Optional[str],
            "params": OrderedDict[str, ParamVal],
            "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]],
        })
    :type other: ```dict```

    :return: IR of updated target. `target` is also updated in-place, and the memory of `other` is used.
    :rtype: ```dict```
    """
    if not target["params"]:
        target["params"] = other["params"]
    elif other["params"]:
        target_params, other_params = map(itemgetter("params"), (target, other))
        target["params"] = merge_params(other_params, target_params)

    if "return_type" not in (target.get("returns") or iter(())):
        target["returns"] = other["returns"]
    elif other["returns"]:
        target["returns"]["return_type"] = _join_non_none(
            target["returns"]["return_type"], other["returns"]["return_type"]
        )
    # if "return_type" in target.get("params", frozenset()):
    #     target["returns"]["return_type"] = _join_non_none(
    #         target["returns"]["return_type"], target["params"].pop("return_type")
    #     )

    other_internal = other.get("_internal", {})
    if other_internal.get("body"):
        if "_internal" in target:
            # Merging internal bodies would be a bad idea IMHO
            target["_internal"].update(other_internal)
        else:
            target["_internal"] = other_internal

    return target

merge_params

merge_params(other_params, target_params)

Merge two ParamVal dicts together. It doesn't do a target_params.update(other_params), instead it carefully merges two collections of dicts.

Parameters:

Name Type Description Default
other_params str

Read-only params to use in update. Iterable of dicts consistent with ParamVal, defined as: TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any})

required
target_params str

The target params to modify. These values take precedence. Iterable of dicts consistent with ParamVal, defined as: TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any})

required

Returns:

Name Type Description
return_type dict

IR of updated target. target is also updated in-place, and the memory of other is used.

Source code in cdd/shared/parse/utils/parser_utils.py
def merge_params(other_params, target_params):
    """
    Merge two ParamVal dicts together. It doesn't do a `target_params.update(other_params)`,
     instead it carefully merges two collections of dicts.

    :param other_params: Read-only params to use in update. Iterable of `dict`s consistent with `ParamVal`, defined as:
        TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any})
    :type other_params: ```Iterable[dict]```

    :param target_params: The target params to modify. These values take precedence. Iterable of `dict`s consistent
      with `ParamVal`, defined as:
        TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any})
    :type target_params: ```Iterable[dict]```

    :return: IR of updated target. `target` is also updated in-place, and the memory of `other` is used.
    :rtype: ```dict```
    """
    for name in other_params.keys() & target_params.keys():
        merge_present_params(other_params[name], target_params[name])
    for name in other_params.keys() - target_params.keys():
        target_params[name] = other_params[name]
    return target_params

merge_present_params

merge_present_params(other_param, target_param)

Merge two ParamVal dicts together. It doesn't do a target_params.update(other_params), instead it carefully merges two dicts.

Parameters:

Name Type Description Default
other_param str

Read-only param to use in update. Dict consistent with ParamVal, defined as: TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any})

required
target_param str

The target param to modify. These values take precedence. Dict consistent with ParamVal, defined as: TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any})

required

Returns:

Name Type Description
return_type dict

IR of updated target. target is also updated in-place, and the memory of other is used.

Source code in cdd/shared/parse/utils/parser_utils.py
def merge_present_params(other_param, target_param):
    """
    Merge two ParamVal dicts together. It doesn't do a `target_params.update(other_params)`,
     instead it carefully merges two dicts.

    :param other_param: Read-only param to use in update. Dict consistent with `ParamVal`, defined as:
        TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any})
    :type other_param: ```dict```

    :param target_param: The target param to modify. These values take precedence. Dict consistent with
      `ParamVal`, defined as:
        TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any})
    :type target_param: ```dict```

    :return: IR of updated target. `target` is also updated in-place, and the memory of `other` is used.
    :rtype: ```dict```
    """
    if not target_param.get("doc") and other_param.get("doc"):
        target_param["doc"] = other_param["doc"]
    if other_param.get("typ") is not None and (
        target_param.get("typ") is None
        or target_param["typ"] in simple_types
        and other_param["typ"] not in simple_types
    ):
        target_param["typ"] = other_param["typ"]
    if (
        target_param.get("default") in none_types
        and other_param.get("default") is not None
    ):
        target_param["default"] = other_param["default"]

cdd.shared.pkg_utils

cdd.shared.pkg_utils

pkg_utils

Parameters:

Name Type Description Default

relative_filename

relative_filename(filename, remove_hints=tuple())

Remove all the paths which are not relevant

Parameters:

Name Type Description Default
filename str

Filename

required
remove_hints tuple[str, ...]

Hints as to what can be removed

required

Returns:

Name Type Description
return_type str

Relative os.path (if derived) else original

Source code in cdd/shared/pkg_utils.py
def relative_filename(filename, remove_hints=tuple()):
    """
    Remove all the paths which are not relevant

    :param filename: Filename
    :type filename: ```str```

    :param remove_hints: Hints as to what can be removed
    :type remove_hints: ```tuple[str, ...]```

    :return: Relative `os.path` (if derived) else original
    :rtype: ```str```
    """
    _filename: str = filename.casefold()
    lib = get_python_lib(), get_python_lib(prefix="")  # type: tuple[str, str]
    return next(
        map(
            lambda elem: filename[len(elem) + 1 :],
            filter(
                lambda elem: _filename.startswith(elem.casefold()), remove_hints + lib
            ),
        ),
        filename,
    )

cdd.shared.pure_utils

cdd.shared.pure_utils

Pure utils for pure functions. For the same input will always produce the same input_str.

Parameters:

Name Type Description Default

FilenameProtocol

Bases: Protocol

Filename protocol

Parameters:

Name Type Description Default

SetEncoder

Bases: JSONEncoder

JSON encoder that supports sets

Parameters:

Name Type Description Default

default

default(obj)

Handle set by giving a sorted list in its place

Parameters:

Name Type Description Default
Source code in cdd/shared/pure_utils.py
def default(self, obj):
    """
    Handle `set` by giving a sorted list in its place
    """
    return (sorted if isinstance(obj, set) else partial(JSONEncoder.default, self))(
        obj
    )

all_dunder_for_module

all_dunder_for_module(module_directory, include, exclude=frozenset(('compound', 'shared', 'tests')), path_validator=path.isdir)

Generate __all__ for a given module using single-level filename hierarchy exclusively

Parameters:

Name Type Description Default
module_directory str

Module path

required
include Iterable[str]

Additional strings to include

required
exclude frozenset

base filenames to ignore

required
path_validator Callable[[str], bool]

Path validation function

required

Returns:

Name Type Description
return_type list[str]

list of strings matching the expected __all__

Source code in cdd/shared/pure_utils.py
def all_dunder_for_module(
    module_directory,
    include,
    exclude=frozenset(("compound", "shared", "tests")),
    path_validator=path.isdir,
):
    """
    Generate `__all__` for a given module using single-level filename hierarchy exclusively

    :param module_directory: Module path
    :type module_directory: ```str```

    :param include: Additional strings to include
    :type include: ```Iterable[str]```

    :param exclude: base filenames to ignore
    :type exclude: ```frozenset```

    :param path_validator: Path validation function
    :type path_validator: ```Callable[[str], bool]```

    :return: list of strings matching the expected `__all__`
    :rtype: ```list[str]```
    """
    return sorted(
        chain.from_iterable(
            (
                include,
                map(
                    itemgetter(0),
                    map(
                        path.splitext,
                        filter(
                            lambda base: path_validator(
                                path.join(module_directory, base)
                            )
                            and not base.startswith("_")
                            and not base.endswith("_utils{}py".format(path.extsep))
                            and base not in exclude,
                            listdir(module_directory),
                        ),
                    ),
                ),
            )
        )
    )

append_to_dict

append_to_dict(d, keys, value)

Append keys to a dictionary in a hierarchical manner and set the last key to a value. For example, given an empty dictionary and keys = [a, b, c] and value = d then the new dictionary would look like the following: {a: {b: {c: d}}}

Parameters:

Name Type Description Default
d dict

dictionary to append keys

required
keys list[str]

keys to append to d

required
value any

value to set keys[-1]

required

Returns:

Name Type Description
return_type dict

dict with new keys and value added

Source code in cdd/shared/pure_utils.py
def append_to_dict(d, keys, value):
    """
    Append keys to a dictionary in a hierarchical manner and set the last key to a value.
    For example, given an empty dictionary and keys = [a, b, c] and value = d then the new
    dictionary would look like the following: {a: {b: {c: d}}}

    :param d: dictionary to append keys
    :type d: ```dict```

    :param keys: keys to append to d
    :type keys: ```list[str]```

    :param value: value to set keys[-1]
    :type value: ```any```

    :return: `dict` with new keys and value added
    :rtype: ```dict```
    """
    pointer = d
    for i, key in enumerate(keys):
        if i == len(keys) - 1 and isinstance(pointer, dict):
            pointer[key] = value
            return d
        if isinstance(pointer, dict):
            pointer.setdefault(key, {})
            pointer = pointer[key]
        else:
            return d
    return d

assert_equal

assert_equal(a, b, cmp=eq)

assert a and b are equal

Parameters:

Name Type Description Default
a Any

anything

required
b Any

anything else

required
cmp Callable[[a, b], bool]

comparator function

required

Returns:

Name Type Description
return_type Literal[True]

True if equal, otherwise raises AssertionError

Source code in cdd/shared/pure_utils.py
def assert_equal(a, b, cmp=eq):
    """
    assert a and b are equal

    :param a: anything
    :type a: ```Any```

    :param b: anything else
    :type b: ```Any```

    :param cmp: comparator function
    :type cmp: ```Callable[[a, b], bool]```

    :return: True if equal, otherwise raises `AssertionError`
    :rtype: ```Literal[True]```
    """
    if not cmp(a, b):
        raise AssertionError("{a!r} != {b!r}".format(a=a, b=b))
    return True

balanced_parentheses

balanced_parentheses(s)

Checks if parentheses are balanced, ignoring whatever is inside quotes

Parameters:

Name Type Description Default
s str

Input string

required

Returns:

Name Type Description
return_type bool

Whether the parens are balanced

Source code in cdd/shared/pure_utils.py
def balanced_parentheses(s):
    """
    Checks if parentheses are balanced, ignoring whatever is inside quotes

    :param s: Input string
    :type s: ```str```

    :return: Whether the parens are balanced
    :rtype: ```bool```
    """
    open_parens: str = "([{"
    closed_parens: str = ")]}"
    counter: Dict[str, int] = {paren: 0 for paren in open_parens + closed_parens}
    quote_mark: Optional[Literal["'", '"']] = None
    for idx, ch in enumerate(s):
        if (
            quote_mark is not None
            and ch == quote_mark
            and (idx == 0 or s[idx - 1] != "\\")
        ):
            quote_mark: Optional[Literal["'", '"']] = None
        elif quote_mark is None:
            if ch in frozenset(("'", '"')):
                quote_mark: Optional[Literal["'", '"']] = cast(Literal["'", '"'], ch)
            elif ch in counter:
                counter[ch] += 1
    return all(
        counter[open_parens[i]] == counter[closed_parens[i]]
        for i in range(len(open_parens))
    )

blockwise

blockwise(t, size=2, fillvalue=None)

Blockwise, like pairwise but with a size parameter From: https://stackoverflow.com/a/4628446

Parameters:

Name Type Description Default
t Iterator

iterator

required
size int

size of each block

required
fillvalue Any

What to use to "pair" with if uneven

required

Returns:

Name Type Description
return_type Iterator

iterator with iterators inside of block size

Source code in cdd/shared/pure_utils.py
def blockwise(t, size=2, fillvalue=None):
    """
    Blockwise, like pairwise but with a `size` parameter
    From: https://stackoverflow.com/a/4628446

    :param t: iterator
    :type t: ```Iterator```

    :param size: size of each block
    :type size: ```int```

    :param fillvalue: What to use to "pair" with if uneven
    :type fillvalue: ```Any```

    :return: iterator with iterators inside of block size
    :rtype: ```Iterator```
    """
    return zip_longest(*[iter(t)] * abs(size), fillvalue=fillvalue)

code_quoted

code_quoted(s)

Internally user-provided None and non literal_evalable input is quoted with ```

This function checks if the input is quoted such

Parameters:

Name Type Description Default
s Any

The input

required

Returns:

Name Type Description
return_type bool

Whether the input is code quoted

Source code in cdd/shared/pure_utils.py
def code_quoted(s):
    """
    Internally user-provided `None` and non `literal_eval`able input is quoted with ```

    This function checks if the input is quoted such

    :param s: The input
    :type s: ```Any```

    :return: Whether the input is code quoted
    :rtype: ```bool```
    """
    return (
        isinstance(s, str) and len(s) > 6 and s.startswith("```") and s.endswith("```")
    )

count_chars_from

count_chars_from(s, sentinel_char_unseen, char, end, s_len=len, start_idx=0, char_f=None)

Count number of chars in string from one or other end, until ignore is no longer True (or entire s is covered)

Parameters:

Name Type Description Default
s str

Input string

required
sentinel_char_unseen bool

Function that takes one char and decided whether to ignore it or not

required
char str

Single character for counting occurrences of

required
end bool

True to look from the end; False to look from start

required
s_len Callable[str, [int]]

String len function to use, override to work at a shorter substr, e.g., lambda _: 5

required
start_idx str

Index to start looking at string from, override to work at a shorter substr, e.g., 3

required
char_f Optional[Callable[[str], bool]]

char function, if True adds 1 to count. Overrides char if provided.

required

Returns:

Name Type Description
return_type int

Number of chars counted (until ignore)

Source code in cdd/shared/pure_utils.py
def count_chars_from(
    s, sentinel_char_unseen, char, end, s_len=len, start_idx=0, char_f=None
):
    """
    Count number of chars in string from one or other end, until `ignore` is no longer True (or entire `s` is covered)

    :param s: Input string
    :type s: ```str``

    :param sentinel_char_unseen: Function that takes one char and decided whether to ignore it or not
    :type sentinel_char_unseen: ```Callable[[str], bool]```

    :param char: Single character for counting occurrences of
    :type char: ```str```

    :param end: True to look from the end; False to look from start
    :type end: ```bool```

    :param s_len: String len function to use, override to work at a shorter substr, e.g., `lambda _: 5`
    :type s_len: ```Callable[str, [int]]```

    :param start_idx: Index to start looking at string from, override to work at a shorter substr, e.g., `3`
    :type start_idx: ```int```

    :param char_f: char function, if `True` adds 1 to count. Overrides `char` if provided.
    :type char_f: ```Optional[Callable[[str], bool]]```

    :return: Number of chars counted (until `ignore`)
    :rtype: ```int```
    """
    char_count: int = 0

    if char_f is None:
        char_f: Callable[[str], bool] = rpartial(eq, char)

    for i in range(*((s_len(s) - 1, start_idx, -1) if end else (start_idx, s_len(s)))):
        if char_f(s[i]):
            char_count += 1
        elif not sentinel_char_unseen(s[i]):
            break
    return char_count

count_iter_items

count_iter_items(iterable)

Consume an iterable not reading it into memory; return the number of items.

Parameters:

Name Type Description Default
iterable Iterable

An iterable

required

Returns:

Name Type Description
return_type int

Number of items in iterable

Source code in cdd/shared/pure_utils.py
def count_iter_items(iterable):
    """
    Consume an iterable not reading it into memory; return the number of items.

    :param iterable: An iterable
    :type iterable: ```Iterable```

    :return: Number of items in iterable
    :rtype: ```int```
    """
    counter: count = count()
    deque(zip(iterable, counter), maxlen=0)
    return next(counter)

deindent

deindent(s, level=None, sep=tab)

Remove all indentation from the input string, or level(s) of indent if specified

Parameters:

Name Type Description Default
s str

Input string

required
level str

Number of tabs to remove from input string or if None: remove all

required
sep str

Separator (usually tab)

required

Returns:

Name Type Description
return_type AnyStr

Deindented string

Source code in cdd/shared/pure_utils.py
def deindent(s, level=None, sep=tab):
    """
    Remove all indentation from the input string, or `level`(s) of indent if specified

    :param s: Input string
    :type s: ```AnyStr```

    :param level: Number of tabs to remove from input string or if None: remove all
    :type level: ```Optional[int]```

    :param sep: Separator (usually `tab`)
    :type sep: ```str```

    :return: Deindented string
    :rtype: ```AnyStr```
    """
    if level is None:
        process_line: Callable[[str], str] = str.lstrip
    else:
        sep *= level

        def process_line(line):
            """
            :param line: The line to dedent
            :type line: ```AnyStr```

            :return: Dedented line
            :rtype: ```AnyStr```
            """
            return line[len(sep) :] if line.startswith(sep) else line

    return "\n".join(map(process_line, s.splitlines()))

diff

diff(input_obj, op)

Given an input with __len__ defined and an op which takes the input and produces one output with __len__ defined, compute the difference and return (diff_len, output)

Parameters:

Name Type Description Default
input_obj Any

The input

required
op Callable[[Any], Sized]

The operation to run

required

Returns:

Name Type Description
return_type tuple[int, Any]

length of difference, response of operated input

Source code in cdd/shared/pure_utils.py
def diff(input_obj, op):
    """
    Given an input with `__len__` defined and an op which takes the input and produces one output
      with `__len__` defined, compute the difference and return (diff_len, output)

    :param input_obj: The input
    :type input_obj: ```Any```

    :param op: The operation to run
    :type op: ```Callable[[Any], Sized]```

    :return: length of difference, response of operated input
    :rtype: ```tuple[int, Any]```
    """
    input_len: int = len(
        input_obj
    )  # Separate line and binding, as `op` could mutate the `input`
    result: Sized = op(input_obj)
    return input_len - len(result), result

emit_separating_tabs

emit_separating_tabs(s, indent_level=1, run_per_line=str.lstrip)

Emit a separating tab between paragraphs

Parameters:

Name Type Description Default
s str

Input string (probably a docstring)

required
indent_level int

docstring indentation level whence: 0=no_tabs, 1=one tab; 2=two tabs

required
run_per_line Callable[[str], str]

Run this function per line

required

Returns:

Name Type Description
return_type str

Original string with a separating tab between paragraphs, & possibly addition indentation on other lines

Source code in cdd/shared/pure_utils.py
def emit_separating_tabs(s, indent_level=1, run_per_line=str.lstrip):
    """
    Emit a separating tab between paragraphs

    :param s: Input string (probably a docstring)
    :type s: ```str```

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

    :param run_per_line: Run this function per line
    :type run_per_line: ```Callable[[str], str]```

    :return: Original string with a separating tab between paragraphs, & possibly addition indentation on other lines
    :rtype: ```str```
    """
    sep: str = tab * indent_level
    return "\n{sep}{}\n{sep}".format(
        run_per_line(
            "\n".join(map(lambda line: sep if len(line) == 0 else line, s.splitlines()))
        ),
        sep=sep,
    )

ensure_valid_identifier

ensure_valid_identifier(s)

Ensure identifier is valid

Parameters:

Name Type Description Default
s str

Potentially valid identifier

required

Returns:

Name Type Description
return_type str

Valid identifier from s

Source code in cdd/shared/pure_utils.py
def ensure_valid_identifier(s):
    """
    Ensure identifier is valid

    :param s: Potentially valid identifier
    :type s: ```str```

    :return: Valid identifier from `s`
    :rtype: ```str```
    """
    if not s:
        return "_"
    elif iskeyword(s):
        return "{}_".format(s)
    elif s[0].isdigit():
        s: str = "_{}".format(s)
    valid: FrozenSet[str] = frozenset(
        "_{}{}".format(string.ascii_letters, string.digits)
    )
    return "".join(filter(valid.__contains__, s)) or "_"

filename_from_mod_or_filename

filename_from_mod_or_filename(mod_or_filename)

Resolve filename from module name or filename

Parameters:

Name Type Description Default
mod_or_filename str

Module name or filename

required

Returns:

Name Type Description
return_type str

Filename

Source code in cdd/shared/pure_utils.py
def filename_from_mod_or_filename(mod_or_filename):
    """
    Resolve filename from module name or filename

    :param mod_or_filename: Module name or filename
    :type mod_or_filename: ```str```

    :return: Filename
    :rtype: ```str```
    """
    filename: FilenameProtocol = cast(
        FilenameProtocol, type("", tuple(), {"origin": mod_or_filename})
    )
    return (
        filename
        if path.sep in mod_or_filename or path.isfile(mod_or_filename)
        else find_spec(mod_or_filename) or filename
    ).origin

find_module_filepath

find_module_filepath(module_name, submodule_name=None, none_when_no_spec=False)

Find module's file location without first importing it

Parameters:

Name Type Description Default
module_name

Module name, e.g., "cdd.tests" or "cdd"

required
```str``` :type: str
required
submodule_name

Submodule name, e.g., "test_pure_utils"

required
```Optional[str]``` :type: Optional[str]
required
none_when_no_spec bool

When find_spec returns None return that. If False raises AssertionError then.

required

Returns:

Name Type Description
return_type

Module location :rpath: str

Source code in cdd/shared/pure_utils.py
def find_module_filepath(module_name, submodule_name=None, none_when_no_spec=False):
    """
    Find module's file location without first importing it

    :param module_name: Module name, e.g., "cdd.tests" or "cdd"
    :type: ```str```

    :param submodule_name: Submodule name, e.g., "test_pure_utils"
    :type: ```Optional[str]```

    :param none_when_no_spec: When `find_spec` returns `None` return that. If `False` raises `AssertionError` then.
    :type none_when_no_spec: ```bool```

    :return: Module location
    :rpath: ```str```
    """
    assert module_name is not None
    module_spec: Optional[ModuleSpec] = find_spec(module_name)
    if module_spec is None:
        if none_when_no_spec:
            return module_spec
        raise AssertionError("spec not found for {}".format(module_name))
    module_origin: Optional[str] = module_spec.origin
    assert module_origin is not None
    module_parent: str = path.dirname(module_origin)
    return (
        module_origin
        if submodule_name is None
        else next(
            filter(
                path.exists,
                (
                    path.join(
                        module_parent,
                        submodule_name,
                        "__init__{}py".format(path.extsep),
                    ),
                    path.join(
                        module_parent, "{}{}py".format(submodule_name, path.extsep)
                    ),
                    path.join(
                        module_parent,
                        submodule_name,
                        "__init__{}py".format(path.extsep),
                    ),
                ),
            ),
            module_origin,
        )
    )

get_module

get_module(name, package=None, extra_symbols=None)

Import a module.

The 'package' argument is required when performing a relative import. It specifies the package to use as the anchor point from which to resolve the relative import to an absolute import.

Wraps importlib.import_module to return the module if it's available in interpreter on ModuleNotFoundError error

Parameters:

Name Type Description Default
name str

Module name

required
package Optional[str]

Package name

required
extra_symbols Optional[dict]

Dictionary of extra symbols to use if importlib.import_module fails

required

Returns:

Name Type Description
return_type Module

Module

Source code in cdd/shared/pure_utils.py
def get_module(name, package=None, extra_symbols=None):
    """
    Import a module.

    The 'package' argument is required when performing a relative import. It
    specifies the package to use as the anchor point from which to resolve the
    relative import to an absolute import.

    Wraps `importlib.import_module` to return the module if it's available in interpreter on ModuleNotFoundError error

    :param name: Module name
    :type name: ```str```

    :param package: Package name
    :type package: ```Optional[str]```

    :param extra_symbols: Dictionary of extra symbols to use if `importlib.import_module` fails
    :type extra_symbols: ```Optional[dict]```

    :return: Module
    :rtype: ```Module```
    """
    try:
        return import_module(name, package)
    except ModuleNotFoundError:
        if name in globals():
            return globals()[name]
        else:
            pkg, _, rest_path = name.partition(".")
            if pkg in extra_symbols:
                return getmodule(
                    (attrgetter(rest_path) if rest_path else identity)(
                        extra_symbols[pkg]
                    )
                )
            raise

identity

identity(*args, **kwargs)

Identity function

Parameters:

Name Type Description Default
args tuple[Any]

Any values

required

Returns:

Name Type Description
return_type Any

the input value

Source code in cdd/shared/pure_utils.py
def identity(*args, **kwargs):
    """
    Identity function

    :param args: Any values
    :type args: ```tuple[Any]```

    :return: the input value
    :rtype: ```Any```
    """
    return args[0] if len(args) == 1 else args

indent_all_but_first

indent_all_but_first(s, indent_level=1, wipe_indents=False, sep=tab)

Indent all lines except the first one

Parameters:

Name Type Description Default
s str

Input string

required
indent_level int

indentation level whence: 0=no_tabs, 1=one tab; 2=two tabs

required
wipe_indents bool

Whether to clean the s of indents first

required
sep str

Separator (usually tab)

required

Returns:

Name Type Description
return_type str

input string indented (except first line)

Source code in cdd/shared/pure_utils.py
def indent_all_but_first(s, indent_level=1, wipe_indents=False, sep=tab):
    """
    Indent all lines except the first one

    :param s: Input string
    :type s: ```str```

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

    :param wipe_indents: Whether to clean the `s` of indents first
    :type wipe_indents: ```bool```

    :param sep: Separator (usually `tab`)
    :type sep: ```str```

    :return: input string indented (except first line)
    :rtype: ```str```
    """
    lines: typing.List[str] = indent(
        deindent(s) if wipe_indents else s, sep * abs(indent_level)
    ).split("\n")
    return "\n".join([lines[0].lstrip()] + lines[1:])

is_ir_empty

is_ir_empty(intermediate_repr)

Checks whether the IR is empty, i.e., might have name and params but will generate a docstr without types or argdoc

Parameters:

Name Type Description Default
intermediate_repr dict

a dictionary consistent with IntermediateRepr, defined as: ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any}) IntermediateRepr = TypedDict("IntermediateRepr", { "name": Optional[str], "type": Optional[str], "doc": Optional[str], "params": OrderedDict[str, ParamVal], "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]], })

required

Returns:

Name Type Description
return_type bool

Whether IR is empty

Source code in cdd/shared/pure_utils.py
def is_ir_empty(intermediate_repr):
    """
    Checks whether the IR is empty, i.e., might have name and params but will generate a docstr without types or argdoc

    :param intermediate_repr: a dictionary consistent with `IntermediateRepr`, defined as:
        ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any})
        IntermediateRepr = TypedDict("IntermediateRepr", {
            "name": Optional[str],
            "type": Optional[str],
            "doc": Optional[str],
            "params": OrderedDict[str, ParamVal],
            "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]],
        })
    :type intermediate_repr: ```dict```

    :return: Whether IR is empty
    :rtype: ```bool```
    """
    return not intermediate_repr.get("doc") and not any(
        param_d is not None and (param_d.get("typ") or param_d.get("doc"))
        for key in ("params", "returns")
        for param_d in (intermediate_repr.get(key) or {}).values()
    )

is_triple_quoted

is_triple_quoted(s)

Whether the str is triple quoted

Parameters:

Name Type Description Default
s str

Input string

required

Returns:

Name Type Description
return_type bool

Whether it has balanced triple quotes (either variety)

Source code in cdd/shared/pure_utils.py
def is_triple_quoted(s):
    """
    Whether the str is triple quoted

    :param s: Input string
    :type s: ```str```

    :return: Whether it has balanced triple quotes (either variety)
    :rtype: ```bool```
    """
    return len(s) > 5 and (
        s.startswith("'''")
        and s.endswith("'''")
        or s.startswith('"""')
        and s.endswith('"""')
    )

location_within

location_within(container, iterable, cmp=eq)

Finds element within iterable within container

Parameters:

Name Type Description Default
container Any

The container, e.g., a str, or list. We are looking for the subset which matches an element in iterable.

required
iterable Any

The iterable, can be constructed

required
cmp Callable[[str, str], bool]

Comparator to check input against

required

Returns:

Name Type Description
return_type tuple[int, int, Optional[Any]]

(Start index iff found else -1, End index iff found else -1, subset iff found else None)

Source code in cdd/shared/pure_utils.py
def location_within(container, iterable, cmp=eq):
    """
    Finds element within iterable within container

    :param container: The container, e.g., a str, or list.
      We are looking for the subset which matches an element in `iterable`.
    :type container: ```Any```

    :param iterable: The iterable, can be constructed
    :type iterable: ```Any```

    :param cmp: Comparator to check input against
    :type cmp: ```Callable[[str, str], bool]```

    :return: (Start index iff found else -1, End index iff found else -1, subset iff found else None)
    :rtype: ```tuple[int, int, Optional[Any]]```
    """
    if not hasattr(container, "__len__"):
        container: Tuple[Any] = tuple(container)
    container_len: int = len(container)

    for elem in iterable:
        elem_len: int = len(elem)
        if elem_len > container_len:
            continue
        elif cmp(elem, container):
            return 0, elem_len, elem
        else:
            for i in range(container_len):
                end: int = i + elem_len
                if cmp(container[i:end], elem):
                    return i, end, elem
                elif i + elem_len + 1 > container_len:
                    break
    return -1, -1, None

lstrip_namespace

lstrip_namespace(s, namespaces)

Remove starting namespace

Parameters:

Name Type Description Default
s str

input string

required
namespaces Union[list[str], tuple[str], Generator[str], Iterator[str]]

namespaces to strip

required

Returns:

Name Type Description
return_type AnyStr

str.lstripped input (potentially just the original!)

Source code in cdd/shared/pure_utils.py
def lstrip_namespace(s, namespaces):
    """
    Remove starting namespace

    :param s: input string
    :type s: ```AnyStr```

    :param namespaces: namespaces to strip
    :type namespaces: ```Union[list[str], tuple[str], Generator[str], Iterator[str]]```

    :return: `str.lstrip`ped input (potentially just the original!)
    :rtype: ```AnyStr```
    """
    for namespace in namespaces:
        s: str = s.lstrip(cast(str, namespace))
    return s

multiline

multiline(s, quote_with=("'", "'"))

For readability and linting, it's useful to turn a long line, like: >>> '''123456789_ 123456789_ 123456789_ 123456789'''

Into:
>>> '''123456789_

''' '''123456789_ ''' '''123456789_ ''' '''123456789'''

Parameters:

Name Type Description Default
s str

Input string

required
quote_with tuple[str, str]

What to quote with

required

Returns:

Name Type Description
return_type str

multine input string

Source code in cdd/shared/pure_utils.py
def multiline(s, quote_with=("'", "'")):
    """
    For readability and linting, it's useful to turn a long line, like:
    >>> '''123456789_\n123456789_\n123456789_\n123456789'''

    Into:
    >>> '''123456789_\n''' \
        '''123456789_\n''' \
        '''123456789_\n''' \
        '''123456789'''

    :param s: Input string
    :type s: ```str```

    :param quote_with: What to quote with
    :type quote_with: ```tuple[str, str]```

    :return: multine input string
    :rtype: ```str```
    """
    return "{}{}".format(
        "",
        tab.join(
            map(
                lambda _s: "{quote_with[0]}{_s}{quote_with[1]} \\\n".format(
                    quote_with=quote_with, _s=_s
                ),
                s.splitlines(),
            )
        ).rstrip(" \n\\"),
    )

namespaced_pascal_to_upper_camelcase

namespaced_pascal_to_upper_camelcase(s, sep='__')

Convert potentially namespaced pascal to upper camelcase

E.g., "foo__bar_can" becomes "Foo__BarCan"

Parameters:

Name Type Description Default
s str

Pascal cased string (potentially with namespace, i.e., sep)

required
sep str

Separator (a.k.a., namespace)

required

Returns:

Name Type Description
return_type str

Upper camel case string (potentially with namespace)

Source code in cdd/shared/pure_utils.py
def namespaced_pascal_to_upper_camelcase(s, sep="__"):
    """
    Convert potentially namespaced pascal to upper camelcase

    E.g., "foo__bar_can" becomes "Foo__BarCan"

    :param s: Pascal cased string (potentially with namespace, i.e., `sep`)
    :type s: ```str```

    :param sep: Separator (a.k.a., namespace)
    :type sep: ```str```

    :return: Upper camel case string (potentially with namespace)
    :rtype: ```str```
    """
    first, sep, last = s.rpartition(sep)
    return "{}{}{}".format(first.title(), sep, pascal_to_upper_camelcase(last))

namespaced_upper_camelcase_to_pascal

namespaced_upper_camelcase_to_pascal(s, sep='__')

Convert potentially namespaced pascal to upper camelcase

E.g., "Foo__BarCan" becomes "foo__bar_can"

Parameters:

Name Type Description Default
s str

Upper camel case string (potentially with namespace, i.e., sep)

required
sep str

Separator (a.k.a., namespace)

required

Returns:

Name Type Description
return_type str

Pascal cased string (potentially with namespace)

Source code in cdd/shared/pure_utils.py
def namespaced_upper_camelcase_to_pascal(s, sep="__"):
    """
    Convert potentially namespaced pascal to upper camelcase

    E.g., "Foo__BarCan" becomes "foo__bar_can"

    :param s: Upper camel case string (potentially with namespace, i.e., `sep`)
    :type s: ```str```

    :param sep: Separator (a.k.a., namespace)
    :type sep: ```str```

    :return: Pascal cased string (potentially with namespace)
    :rtype: ```str```
    """
    first, sep, last = s.rpartition(sep)
    return "{}{}{}".format(first.lower(), sep, upper_camelcase_to_pascal(last))

pairwise

pairwise(iterable)

Return successive overlapping pairs taken from the input iterable.

The number of 2-tuples in the output iterator will be one fewer than the number of inputs. It will be empty if the input iterable has fewer than two values.

https://docs.python.org/3/library/itertools.html#itertools.pairwise but it's only avail. from 3.10 onwards

Parameters:

Name Type Description Default
iterable Iterable

An iterable

required

Returns:

Name Type Description
return_type zip

A pair of 2-tuples or empty

Source code in cdd/shared/pure_utils.py
def pairwise(iterable):
    """
    Return successive overlapping pairs taken from the input iterable.

    The number of 2-tuples in the output iterator will be one fewer than the number of inputs.
    It will be empty if the input iterable has fewer than two values.

    https://docs.python.org/3/library/itertools.html#itertools.pairwise
    but it's only avail. from 3.10 onwards

    :param iterable: An iterable
    :type iterable: ```Iterable```

    :return: A pair of 2-tuples or empty
    :rtype: ```zip```
    """
    # pairwise('ABCDEFG') --> AB BC CD DE EF FG
    a, b = tee(iterable)
    next(b, None)
    return zip(a, b)

paren_wrap_code

paren_wrap_code(code)

The new builtin AST unparser adds extra parentheses, so match that behaviour on older versions

Parameters:

Name Type Description Default
code str

Source code string

required

Returns:

Name Type Description
return_type str

Potentially parenthetically wrapped input

Source code in cdd/shared/pure_utils.py
def paren_wrap_code(code):
    """
    The new builtin AST unparser adds extra parentheses, so match that behaviour on older versions

    :param code: Source code string
    :type code: ```str```

    :return: Potentially parenthetically wrapped input
    :rtype: ```str```
    """
    return (
        "({code})".format(code=code)
        if PY_GTE_3_9 and code[0] + code[-1] not in frozenset(("()", "[]", "{}"))
        else code
    )

parse_comment_from_line

parse_comment_from_line(line)

Remove from comment onwards in line

Parameters:

Name Type Description Default
line str

Python source line

required

Returns:

Name Type Description
return_type str

line without comments

Source code in cdd/shared/pure_utils.py
def parse_comment_from_line(line):
    """
    Remove from comment onwards in line

    :param line: Python source line
    :type line: ```str```

    :return: `line` without comments
    :rtype: ```str```
    """
    double: int = 0
    single: int = 0
    for col, ch in enumerate(line):
        if col > 3 and line[col - 1] == "\\" and ch in frozenset(('"', "'", "#")):
            pass  # Ignore the char
        elif ch == '"':
            double += 1
        elif ch == "'":
            single += 1
        elif ch == "#" and single & 1 == 0 and double & 1 == 0:
            col_offset = (
                col
                if col == 0
                else (col - count_iter_items(takewhile(str.isspace, line[:col][::-1])))
            )
            return "".join(islice(line, 0, col_offset))
    return line

pascal_to_upper_camelcase

pascal_to_upper_camelcase(s)

Transform pascal input to upper camelcase

Parameters:

Name Type Description Default
s str

Pascal cased string

required

Returns:

Name Type Description
return_type str

Upper camel case string

Source code in cdd/shared/pure_utils.py
def pascal_to_upper_camelcase(s):
    """
    Transform pascal input to upper camelcase

    :param s: Pascal cased string
    :type s: ```str```

    :return: Upper camel case string
    :rtype: ```str```
    """
    return "".join(filterfalse(str.isspace, s.title().replace("_", "")))

pluralise

pluralise(singular)

Return plural form of given lowercase singular word (English only). Based on ActiveState recipe http://code.activestate.com/recipes/413172/ and 577781

Note: For production you'd probably want to use nltk or an NLP AI model

Parameters:

Name Type Description Default
singular str

Non-plural

required

Returns:

Name Type Description
return_type str

Plural version

Source code in cdd/shared/pure_utils.py
def pluralise(singular):
    """Return plural form of given lowercase singular word (English only). Based on
    ActiveState recipe http://code.activestate.com/recipes/413172/ and 577781

    Note: For production you'd probably want to use nltk or an NLP AI model

    :param singular: Non-plural
    :type singular: ```str```

    :return: Plural version
    :rtype: ```str```
    """
    if not singular:
        return ""
    plural: str = (
        _ABERRANT_PLURAL_MAP.get(singular) or singular.endswith("es") and singular
    )
    if plural:
        return plural
    root: str = singular
    try:
        if singular[-1] == "y" and singular[-2] not in VOWELS:
            root: str = singular[:-1]
            suffix: str = "ies"
        elif singular[-1] == "s":
            if singular[-2] in VOWELS:
                if singular[-3:] == "ius":
                    root: str = singular[:-2]
                    suffix: str = "i"
                else:
                    root: str = singular[:-1]
                    suffix: str = "ses"
            else:
                suffix: str = "es"
        elif singular[-2:] in ("ch", "sh"):
            suffix: str = "es"
        else:
            suffix: str = "s"
    except IndexError:
        suffix: str = "s"

    return root + suffix

quote

quote(s, mark='"')

Quote the input string if it's not already quoted

Parameters:

Name Type Description Default
s Union[str, float, complex, int, None]

Input string or literal or None

required
mark str

Quote mark to wrap with

required

Returns:

Name Type Description
return_type Union[str, float, complex, int, None]

Quoted string or input (if input is not str)

Source code in cdd/shared/pure_utils.py
def quote(s, mark='"'):
    """
    Quote the input string if it's not already quoted

    :param s: Input string or literal or None
    :type s: ```Union[str, float, complex, int, None]```

    :param mark: Quote mark to wrap with
    :type mark: ```str```

    :return: Quoted string or input (if input is not str)
    :rtype: ```Union[str, float, complex, int, None]```
    """
    very_simple_types = (
        type(None),
        int,
        float,
        complex,
    )  # type: tuple[Type[None], Type[int], Type[float], Type[complex]]
    s: str = (
        s
        if isinstance(s, (str, *very_simple_types))
        else (
            s.s
            if isinstance(s, Str)
            else s.id if isinstance(s, Name) else getattr(s, "value", s)
        )
    )
    # ^ Poor man's `get_value`
    if (
        isinstance(s, very_simple_types)
        or len(s) == 0
        or len(s) > 1
        and s[0] == s[-1]
        and s[0] in frozenset(("'", '"'))
    ):
        return s
    return "{mark}{s}{mark}".format(mark=mark, s=s)

read_file_to_str

read_file_to_str(filename, mode='rt')

Read filename into a str, closing the file afterwards

Parameters:

Name Type Description Default
filename str

Input filename

required
mode str

File mode

required

Returns:

Name Type Description
return_type str

Filename content as str

Source code in cdd/shared/pure_utils.py
def read_file_to_str(filename, mode="rt"):
    """
    Read filename into a str, closing the file afterwards

    :param filename: Input filename
    :type filename: ```str```

    :param mode: File mode
    :type mode: ```str```

    :return: Filename content as str
    :rtype: ```str```
    """
    with open(filename, mode) as f:
        return f.read()

reindent

reindent(s, indent_level=1, join_on='\n')

Reindent the input string

Parameters:

Name Type Description Default
s str

Input string

required
indent_level int

docstring indentation level whence: 0=no_tabs, 1=one tab; 2=two tabs

required
join_on str

What to join on, e.g., ' '

required

Returns:

Name Type Description
return_type AnyStr

Reindented string

Source code in cdd/shared/pure_utils.py
def reindent(s, indent_level=1, join_on="\n"):
    """
    Reindent the input string

    :param s: Input string
    :type s: ```AnyStr```

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

    :param join_on: What to join on, e.g., '\n'
    :type join_on: ```str```

    :return: Reindented string
    :rtype: ```AnyStr```
    """
    return join_on.join(
        map(
            lambda line: "{tab}{line}".format(
                tab=abs(indent_level) * tab, line=line.lstrip()
            ),
            s.split("\n"),
        )
    ).replace(tab, "", 1)

remove_whitespace_comments

remove_whitespace_comments(source)

Remove all insignificant whitespace and comments from source

Parameters:

Name Type Description Default
source str

Python source string

required

Returns:

Name Type Description
return_type str

source without significant whitespace and comments

Source code in cdd/shared/pure_utils.py
def remove_whitespace_comments(source):
    """
    Remove all insignificant whitespace and comments from source

    :param source: Python source string
    :type source: ```str```

    :return: `source` without significant whitespace and comments
    :rtype: ```str```
    """
    return "\n".join(
        filter(
            None,
            filterfalse(str.isspace, map(parse_comment_from_line, source.splitlines())),
        )
    )

rpartial

rpartial(func, *args)

Partially applies last arguments.

Parameters:

Name Type Description Default
Source code in cdd/shared/pure_utils.py
def rpartial(func, *args):
    """Partially applies last arguments."""
    return lambda *a: func(*(a + args))

sanitise

sanitise(s)

Sanitise the input string, appending an _ if it's a keyword

Parameters:

Name Type Description Default
s str

Input string

required

Returns:

Name Type Description
return_type str

input string with '_' append if it's a keyword else input string

Source code in cdd/shared/pure_utils.py
def sanitise(s):
    """
    Sanitise the input string, appending an `_` if it's a keyword

    :param s: Input string
    :type s: ```str```

    :return: input string with '_' append if it's a keyword else input string
    :rtype: ```str```
    """
    return "{}_".format(s) if iskeyword(s) else s

set_attr

set_attr(obj, key, val)

Sets the named attribute on the given object to the specified value.

set_attr(x, 'y', v) is equivalent to ``x.y = v; return x''

Parameters:

Name Type Description Default
obj Any

An object

required
key str

A key

required
val Any

A value

required

Returns:

Name Type Description
return_type Any

The modified obj

Source code in cdd/shared/pure_utils.py
def set_attr(obj, key, val):
    """
    Sets the named attribute on the given object to the specified value.

    set_attr(x, 'y', v) is equivalent to ``x.y = v; return x''

    :param obj: An object
    :type obj: ```Any```

    :param key: A key
    :type key: ```str```

    :param val: A value
    :type val: ```Any```

    :return: The modified `obj`
    :rtype: ```Any```
    """
    setattr(obj, key, val)
    return obj

set_item

set_item(obj, key, val)

Sets the item on the given object to the specified value.

set_item(x, 'y', v) is equivalent to ``x[y] = v; return x''

Parameters:

Name Type Description Default
obj Any

An object

required
key Union[str, int]

A key

required
val Any

A value

required

Returns:

Name Type Description
return_type Any

The modified obj

Source code in cdd/shared/pure_utils.py
def set_item(obj, key, val):
    """
    Sets the item on the given object to the specified value.

    set_item(x, 'y', v) is equivalent to ``x[y] = v; return x''

    :param obj: An object
    :type obj: ```Any```

    :param key: A key
    :type key: ```Union[str, int]```

    :param val: A value
    :type val: ```Any```

    :return: The modified `obj`
    :rtype: ```Any```
    """
    obj[key] = val
    return obj

sliding_window

sliding_window(iterable, n)

Sliding window

https://docs.python.org/3/library/itertools.html#itertools-recipes

Parameters:

Name Type Description Default
iterable Iterable

An iterable

required
n int

Window size

required

Returns:

Name Type Description
return_type Generator[tuple]

sliding window

Source code in cdd/shared/pure_utils.py
def sliding_window(iterable, n):
    """
    Sliding window

    https://docs.python.org/3/library/itertools.html#itertools-recipes

    :param iterable: An iterable
    :type iterable: ```Iterable```

    :param n: Window size
    :type n: ```int```

    :return: sliding window
    :rtype: ```Generator[tuple]```
    """
    # sliding_window('ABCDEFG', 4) --> ABCD BCDE CDEF DEFG
    it = iter(iterable)
    window = deque(islice(it, n - 1), maxlen=n)
    for x in it:
        window.append(x)
        yield tuple(window)

strip_split

strip_split(param, sep)

Split and strip the input string on given separator

Parameters:

Name Type Description Default
param str

Module/ClassDef/FunctionDef/AnnAssign/Assign resolver with a dot syntax.

required
sep str

Separator

required

Returns:

Name Type Description
return_type Iterator[str, ...]

Iterator of each element of the hierarchy

Source code in cdd/shared/pure_utils.py
def strip_split(param, sep):
    """
    Split and strip the input string on given separator

    :param param: Module/ClassDef/FunctionDef/AnnAssign/Assign resolver with a dot syntax.
    :type param: ```str```

    :param sep: Separator
    :type sep: ```str```

    :return: Iterator of each element of the hierarchy
    :rtype: ```Iterator[str, ...]```
    """
    return map(str.strip, param.split(sep))

strip_starting

strip_starting(line, str_to_strip=tab)

Parameters:

Name Type Description Default
line str

Input string

required
str_to_strip str

Removes only this (once… so not str.lstrip) from the start

required
Source code in cdd/shared/pure_utils.py
def strip_starting(line, str_to_strip=tab):
    """
    :param line: Input string
    :type line: ```AnyStr```

    :param str_to_strip: Removes only this (once… so not `str.lstrip`) from the start
    :type str_to_strip: ```str```
    """
    return line[len(str_to_strip) :] if line.startswith(str_to_strip) else line

unquote

unquote(input_str)

Unquote a string. Removes one set of leading quotes ''' or '"'

Parameters:

Name Type Description Default
input_str str

Input string

required

Returns:

Name Type Description
return_type Optional[str]

Unquoted string

Source code in cdd/shared/pure_utils.py
def unquote(input_str):
    """
    Unquote a string. Removes one set of leading quotes `'\''` or `'"'`

    :param input_str: Input string
    :type input_str: ```Optional[str]```

    :return: Unquoted string
    :rtype: ```Optional[str]```
    """
    if (
        isinstance(input_str, str)
        and len(input_str) > 1
        and (
            input_str.startswith('"')
            and input_str.endswith('"')
            or input_str.startswith("'")
            and input_str.endswith("'")
        )
    ):
        return input_str[1:-1]
    return input_str

update_d

update_d(d, arg=None, **kwargs)

Update d inplace

Parameters:

Name Type Description Default
d dict

dict to update

required
arg dict

dict to update with

required
kwargs Optional[dict]

keyword args to update with

required

Returns:

Name Type Description
return_type dict

Updated dict

Source code in cdd/shared/pure_utils.py
def update_d(d, arg=None, **kwargs):
    """
    Update d inplace

    :param d: dict to update
    :type d: ```dict```

    :param arg: dict to update with
    :type arg: ```Optional[dict]```

    :param kwargs: keyword args to update with
    :type kwargs: ```**kwargs```

    :return: Updated dict
    :rtype: ```dict```
    """
    if arg:
        d.update(cast(dict, arg))
    if kwargs:
        d.update(kwargs)
    return d

upper_camelcase_to_pascal

upper_camelcase_to_pascal(s)

Transform upper camelcase input to pascal case

Parameters:

Name Type Description Default
s str

Upper camel case string

required

Returns:

Name Type Description
return_type str

Pascal cased string

Source code in cdd/shared/pure_utils.py
def upper_camelcase_to_pascal(s):
    """
    Transform upper camelcase input to pascal case

    :param s: Upper camel case string
    :type s: ```str```

    :return: Pascal cased string
    :rtype: ```str```
    """
    return "_".join(
        map(
            str.lower,
            "".join((" {}".format(c) if c.isupper() else c) for c in s)
            .lstrip(" ")
            .split(" "),
        )
    )

cdd.shared.source_transformer

cdd.shared.source_transformer

Source transformer module. Uses astor on Python < 3.9

Parameters:

Name Type Description Default

ast_parse

ast_parse(source, filename='<unknown>', mode='exec', skip_annotate=False, skip_docstring_remit=False)

Convert the AST input to Python source string

Parameters:

Name Type Description Default
source

Python source

required
source str
required
filename str

Filename being parsed

required
mode Literal['exec', 'single', 'eval']

'exec' to compile a module, 'single' to compile a single (interactive) statement, or 'eval' to compile an expression.

required
skip_annotate bool

Don't run annotate_ancestry

required
skip_docstring_remit bool

Don't parse & emit the docstring as a replacement for current docstring

required

Returns:

Name Type Description
return_type AST

AST node

Source code in cdd/shared/source_transformer.py
def ast_parse(
    source,
    filename="<unknown>",
    mode="exec",
    skip_annotate=False,
    skip_docstring_remit=False,
):
    """
    Convert the AST input to Python source string

    :param source: Python source
    :type  source: ```str```

    :param filename: Filename being parsed
    :type filename: ```str```

    :param mode: 'exec' to compile a module, 'single' to compile a single (interactive) statement,
      or 'eval' to compile an expression.
    :type mode: ```Literal['exec', 'single', 'eval']```

    :param skip_annotate: Don't run `annotate_ancestry`
    :type skip_annotate: ```bool```

    :param skip_docstring_remit: Don't parse & emit the docstring as a replacement for current docstring
    :type skip_docstring_remit: ```bool```

    :return: AST node
    :rtype: ```AST```
    """
    parsed_ast = parse(source, filename=filename, mode=mode)
    if not skip_annotate:
        cdd.shared.ast_utils.annotate_ancestry(parsed_ast, filename=filename)
        setattr(parsed_ast, "__file__", filename)
    if not skip_docstring_remit and isinstance(
        parsed_ast, (Module, ClassDef, FunctionDef, AsyncFunctionDef)
    ):
        docstring: Optional[str] = get_docstring(parsed_ast, clean=True)
        if docstring is None:
            return parsed_ast

        # Reindent docstring
        parsed_ast.body[0].value.value = "\n{tab}{docstring}\n{tab}".format(
            tab=tab, docstring=reindent(docstring)
        )
    return parsed_ast

to_code

to_code(node)

Convert the AST input to Python source string

Parameters:

Name Type Description Default
node AST

AST node

required

Returns:

Name Type Description
return_type str

Python source

Source code in cdd/shared/source_transformer.py
def to_code(node):
    """
    Convert the AST input to Python source string

    :param node: AST node
    :type node: ```AST```

    :return: Python source
    :rtype: ```str```
    """
    # ^Not `to_code = getattr…` so docstring can be included^
    return unparse(node)

cdd.shared.types

cdd.shared.types

Shared types

Parameters:

Name Type Description Default

cdd.sqlalchemy

cdd.sqlalchemy

SQLalchemy parsers and emitters module

Parameters:

Name Type Description Default

cdd.sqlalchemy.emit

cdd.sqlalchemy.emit

SQLalchemy emitters

Parameters:

Name Type Description Default

sqlalchemy

sqlalchemy(intermediate_repr, emit_repr=True, class_name=None, class_bases=('Base',), decorator_list=None, table_name=None, force_pk_id=FORCE_PK_ID, docstring_format='rest', word_wrap=True, emit_original_whitespace=False, emit_default_doc=True)

Construct an SQLAlchemy declarative class

Parameters:

Name Type Description Default
intermediate_repr dict

a dictionary consistent with IntermediateRepr, defined as: ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any}) IntermediateRepr = TypedDict("IntermediateRepr", { "name": Optional[str], "type": Optional[str], "doc": Optional[str], "params": OrderedDict[str, ParamVal], "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]], })

required
emit_repr bool

Whether to generate a __repr__ method

required
class_name name

name of class

required
class_bases Iterable[str]

bases of class (the generated class will inherit these)

required
decorator_list Optional[Union[List[str], List[]]]

List of decorators

required
table_name str

Table name, defaults to class_name

class_name
force_pk_id bool

Whether to force primary_key to be named id (if there isn't already a primary_key)

required
docstring_format Literal['rest', 'numpydoc', 'google']

Format of docstring

required
word_wrap bool

Whether to word-wrap. Set DOCTRANS_LINE_LENGTH to configure length.

required
emit_original_whitespace bool

Whether to emit an original whitespace (in docstring) or strip it out

required
emit_default_doc bool

Whether help/docstring should include 'With default' text

required

Returns:

Name Type Description
return_type ClassDef

SQLalchemy declarative class AST

Source code in cdd/sqlalchemy/emit.py
def sqlalchemy(
    intermediate_repr,
    emit_repr=True,
    class_name=None,
    class_bases=("Base",),
    decorator_list=None,
    table_name=None,
    force_pk_id=FORCE_PK_ID,
    docstring_format="rest",
    word_wrap=True,
    emit_original_whitespace=False,
    emit_default_doc=True,
):
    """
    Construct an SQLAlchemy declarative class

    :param intermediate_repr: a dictionary consistent with `IntermediateRepr`, defined as:
        ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any})
        IntermediateRepr = TypedDict("IntermediateRepr", {
            "name": Optional[str],
            "type": Optional[str],
            "doc": Optional[str],
            "params": OrderedDict[str, ParamVal],
            "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]],
        })
    :type intermediate_repr: ```dict```

    :param emit_repr: Whether to generate a `__repr__` method
    :type emit_repr: ```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 table_name: Table name, defaults to `class_name`
    :type table_name: ```str```

    :param force_pk_id: Whether to force primary_key to be named `id` (if there isn't already a primary_key)
    :type force_pk_id: ```bool```

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

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

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

    :param emit_original_whitespace: Whether to emit an original whitespace (in docstring) or strip it out
    :type emit_original_whitespace: ```bool```

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

    :return: SQLalchemy declarative class AST
    :rtype: ```ClassDef```
    """

    if class_name is None and intermediate_repr["name"]:
        class_name: Optional[str] = intermediate_repr["name"]
    assert class_name is not None, "`class_name` is `None`"

    return ClassDef(
        name=class_name,
        bases=list(
            map(
                lambda class_base: Name(
                    class_base, Load(), lineno=None, col_offset=None
                ),
                class_bases,
            )
        ),
        decorator_list=decorator_list or [],
        type_params=[],
        keywords=[],
        body=list(
            filter(
                None,
                chain.from_iterable(
                    (
                        (
                            (
                                Expr(
                                    set_value(
                                        concat_with_whitespace(
                                            *map(
                                                partial(
                                                    docstring,
                                                    docstring_format=docstring_format,
                                                    emit_default_doc=emit_default_doc,
                                                    emit_original_whitespace=emit_original_whitespace,
                                                    emit_separating_tab=True,
                                                    emit_types=True,
                                                    indent_level=1,
                                                    word_wrap=word_wrap,
                                                ),
                                                (
                                                    {
                                                        "doc": intermediate_repr["doc"],
                                                        "params": OrderedDict(),
                                                        "returns": None,
                                                    },
                                                    {
                                                        "doc": "",
                                                        "params": OrderedDict(),
                                                        "returns": intermediate_repr[
                                                            "returns"
                                                        ],
                                                    },
                                                ),
                                            )
                                        )
                                    ),
                                    lineno=None,
                                    col_offset=None,
                                )
                                if intermediate_repr.get("doc")
                                or (intermediate_repr["returns"] or {})
                                .get("return_type", {})
                                .get("doc")
                                else None
                            ),
                            Assign(
                                targets=[
                                    Name(
                                        "__tablename__",
                                        Store(),
                                        lineno=None,
                                        col_offset=None,
                                    )
                                ],
                                value=set_value(table_name or class_name),
                                expr=None,
                                lineno=None,
                                **maybe_type_comment,
                            ),
                        ),
                        *map(
                            lambda name_param: map(
                                lambda column: Assign(
                                    targets=[
                                        Name(
                                            name_param[0],
                                            Store(),
                                            lineno=None,
                                            col_offset=None,
                                        )
                                    ],
                                    value=column,
                                    expr=None,
                                    lineno=None,
                                    **maybe_type_comment,
                                ),
                                cdd.sqlalchemy.utils.emit_utils.param_to_sqlalchemy_column_calls(
                                    name_param, include_name=False
                                ),
                            ),
                            cdd.sqlalchemy.utils.emit_utils.ensure_has_primary_key(
                                intermediate_repr["params"], force_pk_id
                            ).items(),
                        ),
                        (
                            (
                                cdd.sqlalchemy.utils.emit_utils.generate_repr_method(
                                    intermediate_repr["params"],
                                    class_name,
                                    docstring_format,
                                )
                                if emit_repr
                                else None
                            ),
                        ),
                    )
                ),
            )
        ),
        expr=None,
        identifier_name=None,
        lineno=None,
        col_offset=None,
    )

sqlalchemy_hybrid

sqlalchemy_hybrid(intermediate_repr, emit_repr=True, emit_create_from_attr=True, class_name=None, class_bases=('Base',), decorator_list=None, table_name=None, force_pk_id=FORCE_PK_ID, docstring_format='rest', word_wrap=True, emit_original_whitespace=False, emit_default_doc=True)

Construct an class TableName(Base): __table__ = sqlalchemy.Table(name, metadata, Column(…), …)

Valid in SQLalchemy 2.0 and 1.4: - docs.sqlalchemy.org/en/14/orm/declarative_tables.html#declarative-with-imperative-table-a-k-a-hybrid-declarative - docs.sqlalchemy.org/en/20/orm/declarative_tables.html#declarative-with-imperative-table-a-k-a-hybrid-declarative

Parameters:

Name Type Description Default
intermediate_repr dict

a dictionary consistent with IntermediateRepr, defined as: ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any}) IntermediateRepr = TypedDict("IntermediateRepr", { "name": Optional[str], "type": Optional[str], "doc": Optional[str], "params": OrderedDict[str, ParamVal], "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]], })

required
emit_repr bool

Whether to generate a __repr__ method

required
emit_create_from_attr bool

Whether to generate a create_from_attr staticmethod

required
class_name name

name of class

required
class_bases Iterable[str]

bases of class (the generated class will inherit these)

required
decorator_list Optional[Union[List[str], List[]]]

List of decorators

required
table_name str

Table name, defaults to class_name

class_name
force_pk_id bool

Whether to force primary_key to be named id (if there isn't already a primary_key)

required
docstring_format Literal['rest', 'numpydoc', 'google']

Format of docstring

required
word_wrap bool

Whether to word-wrap. Set DOCTRANS_LINE_LENGTH to configure length.

required
emit_original_whitespace bool

Whether to emit an original whitespace (in docstring) or strip it out

required
emit_default_doc bool

Whether help/docstring should include 'With default' text

required

Returns:

Name Type Description
return_type ClassDef

SQLalchemy hybrids declarative class AST

Source code in cdd/sqlalchemy/emit.py
def sqlalchemy_hybrid(
    intermediate_repr,
    emit_repr=True,
    emit_create_from_attr=True,
    class_name=None,
    class_bases=("Base",),
    decorator_list=None,
    table_name=None,
    force_pk_id=FORCE_PK_ID,
    docstring_format="rest",
    word_wrap=True,
    emit_original_whitespace=False,
    emit_default_doc=True,
):
    """
    Construct an `class TableName(Base): __table__ = sqlalchemy.Table(name, metadata, Column(…), …)`

    Valid in SQLalchemy 2.0 and 1.4:
    - docs.sqlalchemy.org/en/14/orm/declarative_tables.html#declarative-with-imperative-table-a-k-a-hybrid-declarative
    - docs.sqlalchemy.org/en/20/orm/declarative_tables.html#declarative-with-imperative-table-a-k-a-hybrid-declarative

    :param intermediate_repr: a dictionary consistent with `IntermediateRepr`, defined as:
        ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any})
        IntermediateRepr = TypedDict("IntermediateRepr", {
            "name": Optional[str],
            "type": Optional[str],
            "doc": Optional[str],
            "params": OrderedDict[str, ParamVal],
            "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]],
        })
    :type intermediate_repr: ```dict```

    :param emit_repr: Whether to generate a `__repr__` method
    :type emit_repr: ```bool```

    :param emit_create_from_attr: Whether to generate a `create_from_attr` staticmethod
    :type emit_create_from_attr: ```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 table_name: Table name, defaults to `class_name`
    :type table_name: ```str```

    :param force_pk_id: Whether to force primary_key to be named `id` (if there isn't already a primary_key)
    :type force_pk_id: ```bool```

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

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

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

    :param emit_original_whitespace: Whether to emit an original whitespace (in docstring) or strip it out
    :type emit_original_whitespace: ```bool```

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

    :return: SQLalchemy hybrids declarative class AST
    :rtype: ```ClassDef```
    """

    if class_name is None and intermediate_repr["name"]:
        class_name: str = intermediate_repr["name"]
    assert class_name is not None, "`class_name` is `None`"

    return ClassDef(
        name=class_name,
        bases=list(
            map(
                lambda class_base: Name(
                    class_base, Load(), lineno=None, col_offset=None
                ),
                class_bases,
            )
        ),
        decorator_list=decorator_list or [],
        type_params=[],
        keywords=[],
        body=list(
            filter(
                None,
                (
                    (
                        Expr(
                            set_value(
                                concat_with_whitespace(
                                    *map(
                                        partial(
                                            docstring,
                                            docstring_format=docstring_format,
                                            emit_default_doc=emit_default_doc,
                                            emit_original_whitespace=emit_original_whitespace,
                                            emit_separating_tab=True,
                                            emit_types=True,
                                            indent_level=1,
                                            word_wrap=word_wrap,
                                        ),
                                        (
                                            {
                                                "doc": intermediate_repr["doc"],
                                                "params": OrderedDict(),
                                                "returns": None,
                                            },
                                            {
                                                "doc": "",
                                                "params": OrderedDict(),
                                                "returns": intermediate_repr["returns"],
                                            },
                                        ),
                                    )
                                )
                            ),
                            lineno=None,
                            col_offset=None,
                        )
                        if intermediate_repr.get("doc")
                        or (intermediate_repr["returns"] or {})
                        .get("return_type", {})
                        .get("doc")
                        else None
                    ),
                    Assign(
                        targets=[
                            Name("__tablename__", Store(), lineno=None, col_offset=None)
                        ],
                        value=set_value(table_name or class_name),
                        expr=None,
                        lineno=None,
                        **maybe_type_comment,
                    ),
                    sqlalchemy_table(
                        intermediate_repr=intermediate_repr,
                        name="__table__",
                        table_name=table_name or intermediate_repr["name"],
                        force_pk_id=force_pk_id,
                        docstring_format=docstring_format,
                        word_wrap=word_wrap,
                        emit_original_whitespace=emit_original_whitespace,
                        emit_default_doc=emit_default_doc,
                    ),
                    (
                        cdd.sqlalchemy.utils.emit_utils.generate_repr_method(
                            intermediate_repr["params"],
                            class_name,
                            docstring_format,
                            hybrid=True,
                        )
                        if emit_repr
                        else None
                    ),
                    (
                        cdd.sqlalchemy.utils.emit_utils.generate_create_from_attr_staticmethod(
                            intermediate_repr["params"], class_name, docstring_format
                        )
                        if emit_create_from_attr
                        else None
                    ),
                ),
            )
        ),
        expr=None,
        identifier_name=None,
        lineno=None,
        col_offset=None,
    )

sqlalchemy_table

sqlalchemy_table(intermediate_repr, name='config_tbl', table_name=None, force_pk_id=FORCE_PK_ID, docstring_format='rest', word_wrap=True, emit_original_whitespace=False, emit_default_doc=True)

Construct an name = sqlalchemy.Table(name, metadata, Column(…), …)

Parameters:

Name Type Description Default
intermediate_repr dict

a dictionary consistent with IntermediateRepr, defined as: ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any}) IntermediateRepr = TypedDict("IntermediateRepr", { "name": Optional[str], "type": Optional[str], "doc": Optional[str], "params": OrderedDict[str, ParamVal], "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]], })

required
name str

name of binding + table

required
table_name str

Table name, defaults to name

name
force_pk_id bool

Whether to force primary_key to be named id (if there isn't already a primary_key)

required
docstring_format Literal['rest', 'numpydoc', 'google']

Format of docstring

required
word_wrap bool

Whether to word-wrap. Set DOCTRANS_LINE_LENGTH to configure length.

required
emit_original_whitespace bool

Whether to emit original whitespace or strip it out

required
emit_default_doc bool

Whether help/docstring should include 'With default' text

required

Returns:

Name Type Description
return_type ClassDef

AST of the Table expression + assignment

Source code in cdd/sqlalchemy/emit.py
def sqlalchemy_table(
    intermediate_repr,
    name="config_tbl",
    table_name=None,
    force_pk_id=FORCE_PK_ID,
    docstring_format="rest",
    word_wrap=True,
    emit_original_whitespace=False,
    emit_default_doc=True,
):
    """
    Construct an `name = sqlalchemy.Table(name, metadata, Column(…), …)`

    :param intermediate_repr: a dictionary consistent with `IntermediateRepr`, defined as:
        ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any})
        IntermediateRepr = TypedDict("IntermediateRepr", {
            "name": Optional[str],
            "type": Optional[str],
            "doc": Optional[str],
            "params": OrderedDict[str, ParamVal],
            "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]],
        })
    :type intermediate_repr: ```dict```

    :param name: name of binding + table
    :type name: ```str```

    :param table_name: Table name, defaults to `name`
    :type table_name: ```str```

    :param force_pk_id: Whether to force primary_key to be named `id` (if there isn't already a primary_key)
    :type force_pk_id: ```bool```

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

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

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

    :param emit_original_whitespace: Whether to emit an original whitespace (in docstring) or strip it out
    :type emit_original_whitespace: ```bool```

    :param emit_original_whitespace: Whether to emit original whitespace or strip it out
    :type emit_original_whitespace: ```bool```

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

    :return: AST of the Table expression + assignment
    :rtype: ```ClassDef```
    """
    return Assign(
        targets=[
            Name(
                ensure_valid_identifier(
                    name
                    if name not in (None, "config_tbl") or not intermediate_repr["name"]
                    else intermediate_repr["name"]
                ),
                Store(),
            )
        ],
        value=Call(
            func=Name("Table", Load(), lineno=None, col_offset=None),
            args=list(
                chain.from_iterable(
                    (
                        iter(
                            (
                                set_value(name if table_name is None else table_name),
                                Name("metadata", Load(), lineno=None, col_offset=None),
                            )
                        ),
                        *map(
                            partial(
                                cdd.sqlalchemy.utils.emit_utils.param_to_sqlalchemy_column_calls,
                                include_name=True,
                            ),
                            cdd.sqlalchemy.utils.emit_utils.ensure_has_primary_key(
                                intermediate_repr["params"], force_pk_id
                            ).items(),
                        ),
                    )
                )
            ),
            keywords=list(
                chain.from_iterable(
                    (
                        (
                            (
                                (
                                    lambda val: (
                                        (
                                            keyword(
                                                arg="comment",
                                                value=set_value(val),
                                                identifier=None,
                                                expr=None,
                                                lineno=None,
                                                **maybe_type_comment,
                                            ),
                                        )
                                        if val
                                        else iter(())
                                    )
                                )(
                                    deindent(
                                        add(
                                            *map(
                                                partial(
                                                    docstring,
                                                    emit_default_doc=emit_default_doc,
                                                    docstring_format=docstring_format,
                                                    word_wrap=word_wrap,
                                                    emit_original_whitespace=emit_original_whitespace,
                                                    emit_types=True,
                                                ),
                                                (
                                                    {
                                                        "doc": (
                                                            intermediate_repr[
                                                                "doc"
                                                            ].lstrip()
                                                            + "\n\n"
                                                            if intermediate_repr[
                                                                "returns"
                                                            ]
                                                            else ""
                                                        ),
                                                        "params": OrderedDict(),
                                                        "returns": None,
                                                    },
                                                    {
                                                        "doc": "",
                                                        "params": OrderedDict(),
                                                        "returns": intermediate_repr[
                                                            "returns"
                                                        ],
                                                    },
                                                ),
                                            )
                                        ).strip()
                                    )
                                )
                                if intermediate_repr.get("doc")
                                else iter(())
                            )
                        ),
                        (
                            keyword(
                                arg="keep_existing",
                                value=set_value(True),
                                identifier=None,
                                expr=None,
                                lineno=None,
                                **maybe_type_comment,
                            ),
                        ),
                    )
                )
            ),
            expr=None,
            expr_func=None,
            lineno=None,
            col_offset=None,
        ),
        lineno=None,
        expr=None,
        **maybe_type_comment,
    )

cdd.sqlalchemy.parse

cdd.sqlalchemy.parse

SQLalchemy parsers

TODO

  • Implement update (see https://github.com/sqlalchemy/sqlalchemy/discussions/5940)
  • Implement batch CRUD

Parameters:

Name Type Description Default

sqlalchemy

sqlalchemy(class_def, parse_original_whitespace=False)

Parse out a class C(Base): __tablename__= 'tbl'; dataset_name = Column(String, doc="p", primary_key=True), as constructed on an SQLalchemy declarative Base.

Also supports hybrid syntax: class TableName(Base): __table__ = sqlalchemy.Table(name, metadata, Column(…), …)

Parameters:

Name Type Description Default
class_def Union[ClassDef]

A class inheriting from declarative Base, where Base = sqlalchemy.orm.declarative_base()

required
parse_original_whitespace bool

Whether to parse original whitespace or strip it out

required

Returns:

Name Type Description
return_type dict

a dictionary consistent with IntermediateRepr, defined as: ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any}) IntermediateRepr = TypedDict("IntermediateRepr", { "name": Optional[str], "type": Optional[str], "doc": Optional[str], "params": OrderedDict[str, ParamVal], "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]], })

Source code in cdd/sqlalchemy/parse.py
def sqlalchemy(class_def, parse_original_whitespace=False):
    """
    Parse out a `class C(Base): __tablename__=  'tbl'; dataset_name = Column(String, doc="p", primary_key=True)`,
        as constructed on an SQLalchemy declarative `Base`.

    Also supports hybrid syntax: `class TableName(Base): __table__ = sqlalchemy.Table(name, metadata, Column(…), …)`

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

    :param parse_original_whitespace: Whether to parse original whitespace or strip it out
    :type parse_original_whitespace: ```bool```

    :return: a dictionary consistent with `IntermediateRepr`, defined as:
        ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any})
        IntermediateRepr = TypedDict("IntermediateRepr", {
            "name": Optional[str],
            "type": Optional[str],
            "doc": Optional[str],
            "params": OrderedDict[str, ParamVal],
            "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]],
        })
    :rtype: ```dict```
    """

    if not isinstance(class_def, ClassDef):
        class_def: ClassDef = (
            next(filter(rpartial(isinstance, ClassDef), class_def.body))
            if isinstance(class_def, Module)
            else ast.parse(getsource(class_def)).body[0]
        )
    assert isinstance(class_def, ClassDef), "Expected `ClassDef` got `{!r}`".format(
        type(class_def).__name__
    )

    return sqlalchemy_table(
        sqlalchemy_class_to_table(class_def, parse_original_whitespace)
    )

sqlalchemy_hybrid

sqlalchemy_hybrid(class_def, parse_original_whitespace=False)

Parse out a class TableName(Base): __table__ = sqlalchemy.Table(name, metadata, Column(…), …), as constructed on an SQLalchemy declarative Base.

Parameters:

Name Type Description Default
class_def Union[ClassDef]

A class inheriting from declarative Base, where Base = sqlalchemy.orm.declarative_base()

required
parse_original_whitespace bool

Whether to parse original whitespace or strip it out

required

Returns:

Name Type Description
return_type dict

a dictionary consistent with IntermediateRepr, defined as: ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any}) IntermediateRepr = TypedDict("IntermediateRepr", { "name": Optional[str], "type": Optional[str], "doc": Optional[str], "params": OrderedDict[str, ParamVal], "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]], })

Source code in cdd/sqlalchemy/parse.py
def sqlalchemy_hybrid(class_def, parse_original_whitespace=False):
    """
    Parse out a `class TableName(Base): __table__ = sqlalchemy.Table(name, metadata, Column(…), …)`,
        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]```

    :param parse_original_whitespace: Whether to parse original whitespace or strip it out
    :type parse_original_whitespace: ```bool```

    :return: a dictionary consistent with `IntermediateRepr`, defined as:
        ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any})
        IntermediateRepr = TypedDict("IntermediateRepr", {
            "name": Optional[str],
            "type": Optional[str],
            "doc": Optional[str],
            "params": OrderedDict[str, ParamVal],
            "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]],
        })
    :rtype: ```dict```
    """
    return sqlalchemy(
        class_def=class_def, parse_original_whitespace=parse_original_whitespace
    )

sqlalchemy_table

sqlalchemy_table(call_or_name, parse_original_whitespace=False)

Parse out a sqlalchemy.Table, or a name = sqlalchemy.Table, into the IR

Parameters:

Name Type Description Default
call_or_name Union[AnnAssign, Assign, Call]

The call to sqlalchemy.Table or an assignment followed by the call

required
parse_original_whitespace bool

Whether to parse original whitespace or strip it out

required

Returns:

Name Type Description
return_type dict

a dictionary consistent with IntermediateRepr, defined as: ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any}) IntermediateRepr = TypedDict("IntermediateRepr", { "name": Optional[str], "type": Optional[str], "doc": Optional[str], "params": OrderedDict[str, ParamVal], "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]], })

Source code in cdd/sqlalchemy/parse.py
def sqlalchemy_table(call_or_name, parse_original_whitespace=False):
    """
    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]```

    :param parse_original_whitespace: Whether to parse original whitespace or strip it out
    :type parse_original_whitespace: ```bool```

    :return: a dictionary consistent with `IntermediateRepr`, defined as:
        ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any})
        IntermediateRepr = TypedDict("IntermediateRepr", {
            "name": Optional[str],
            "type": Optional[str],
            "doc": Optional[str],
            "params": OrderedDict[str, ParamVal],
            "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]],
        })
    :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: Optional[str] = next(
        map(
            get_value,
            map(
                get_value, filter(lambda kw: kw.arg == "comment", call_or_name.keywords)
            ),
        ),
        None,
    )
    doc: Optional[str] = next(
        map(
            get_value,
            map(get_value, filter(lambda kw: kw.arg == "doc", call_or_name.keywords)),
        ),
        None,
    )
    intermediate_repr: IntermediateRepr = (
        {"type": None, "doc": "", "params": OrderedDict()}
        if comment is None and doc is None
        else docstring(
            doc or comment, parse_original_whitespace=parse_original_whitespace
        )
    )
    intermediate_repr["name"] = name
    assert isinstance(call_or_name, Call), "Expected `all` got `{node_name!r}`".format(
        node_name=type(call_or_name).__name__
    )
    assert_equal(call_or_name.func.id.rpartition(".")[2], "Table")
    assert len(call_or_name.args) > 2

    # Partial[IntermediateRepr]
    merge_ir = {
        "params": OrderedDict(map(column_call_to_param, call_or_name.args[2:])),
        "returns": None,
    }
    cdd.shared.parse.utils.parser_utils.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

cdd.sqlalchemy.utils

cdd.sqlalchemy.utils

SQLalchemy parsers and emitters utility module

Parameters:

Name Type Description Default

cdd.sqlalchemy.utils.emit_utils

cdd.sqlalchemy.utils.emit_utils

Utility functions for cdd.sqlalchemy.emit

Parameters:

Name Type Description Default

ensure_has_primary_key

ensure_has_primary_key(intermediate_repr, force_pk_id=False)

Add a primary key to the input (if nonexistent) then return the input.

Parameters:

Name Type Description Default
intermediate_repr dict

a dictionary consistent with IntermediateRepr, defined as: ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any}) IntermediateRepr = TypedDict("IntermediateRepr", { "name": Optional[str], "type": Optional[str], "doc": Optional[str], "params": OrderedDict[str, ParamVal], "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]], })

required
force_pk_id bool

Whether to force primary_key to be named id (if there isn't already a primary_key)

required

Returns:

Name Type Description
return_type dict

a dictionary consistent with IntermediateRepr, defined as: ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any}) IntermediateRepr = TypedDict("IntermediateRepr", { "name": Optional[str], "type": Optional[str], "doc": Optional[str], "params": OrderedDict[str, ParamVal], "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]], })

Source code in cdd/sqlalchemy/utils/emit_utils.py
def ensure_has_primary_key(intermediate_repr, force_pk_id=False):
    """
    Add a primary key to the input (if nonexistent) then return the input.

    :param intermediate_repr: a dictionary consistent with `IntermediateRepr`, defined as:
        ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any})
        IntermediateRepr = TypedDict("IntermediateRepr", {
            "name": Optional[str],
            "type": Optional[str],
            "doc": Optional[str],
            "params": OrderedDict[str, ParamVal],
            "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]],
        })
    :type intermediate_repr: ```dict```

    :param force_pk_id: Whether to force primary_key to be named `id` (if there isn't already a primary_key)
    :type force_pk_id: ```bool```

    :return: a dictionary consistent with `IntermediateRepr`, defined as:
        ParamVal = TypedDict("ParamVal", {"typ": str, "doc": Optional[str], "default": Any})
        IntermediateRepr = TypedDict("IntermediateRepr", {
            "name": Optional[str],
            "type": Optional[str],
            "doc": Optional[str],
            "params": OrderedDict[str, ParamVal],
            "returns": Optional[OrderedDict[Literal["return_type"], ParamVal]],
        })
    :rtype: ```dict```
    """
    params: OrderedDict[str, ParamVal] = (
        intermediate_repr
        if isinstance(intermediate_repr, OrderedDict)
        else intermediate_repr["params"]
    )
    if not any(
        filter(
            rpartial(str.startswith, "[PK]"),
            map(
                methodcaller("get", "doc", ""),
                params.values(),
            ),
        )
    ):
        candidate_pks: List[str] = []
        deque(
            map(
                candidate_pks.append,
                filter(
                    lambda k: "_name" in k or "_id" in k or "id_" in k or k == "id",
                    params.keys(),
                ),
            ),
            maxlen=0,
        )
        if not force_pk_id and len(candidate_pks) == 1:
            params[candidate_pks[0]]["doc"] = (
                "[PK] {}".format(params[candidate_pks[0]]["doc"])
                if params[candidate_pks[0]].get("doc")
                else "[PK]"
            )
        elif "id" in intermediate_repr.get("params", iter(())):
            params["id"]["doc"] = (
                "[PK] {}".format(params["id"]["doc"])
                if params["id"].get("doc")
                else "[PK]"
            )
        else:
            assert "id" not in intermediate_repr.get(
                "params", iter(())
            ), "Primary key unable to infer and column `id` already taken"
            params["id"] = {
                "doc": "[PK]",
                "typ": "int",
                "x_typ": {
                    "sql": {
                        "constraints": {
                            "server_default": Call(
                                args=[],
                                func=Name(
                                    "Identity", Load(), lineno=None, col_offset=None
                                ),
                                keywords=[],
                                lineno=None,
                                col_offset=None,
                            )
                        }
                    }
                },
            }
    return intermediate_repr

generate_create_from_attr_staticmethod

generate_create_from_attr_staticmethod(params, cls_name, docstring_format)

Generate a __repr__ method with all params, using str.format syntax

Parameters:

Name Type Description Default
params OrderedDict

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

required
cls_name str

Name of class

required
docstring_format Literal['rest', 'numpydoc', 'google']

Format of docstring

required

Returns:

Name Type Description
return_type FunctionDef

__repr__ method

Source code in cdd/sqlalchemy/utils/emit_utils.py
def generate_create_from_attr_staticmethod(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']```

    :return: `__repr__` method
    :rtype: ```FunctionDef```
    """
    keys = tuple(params.keys())  # type: tuple[str, ...]
    return FunctionDef(
        name="create_from_attr",
        args=arguments(
            posonlyargs=[],
            arg=None,
            args=[cdd.shared.ast_utils.set_arg("record")],
            kwonlyargs=[],
            kw_defaults=[],
            defaults=[],
            vararg=None,
            kwarg=None,
        ),
        body=[
            Expr(
                cdd.shared.ast_utils.set_value(
                    """\n{sep}{_repr_docstring}""".format(
                        sep=tab * 2,
                        _repr_docstring=(
                            docstring_create_from_attr_str
                            if docstring_format == "rest"
                            else docstring_create_from_attr_google_str
                        )
                        .replace("self", cls_name)
                        .lstrip(),
                    )
                ),
                lineno=None,
                col_offset=None,
            ),
            Return(
                value=Call(
                    func=Name(cls_name, Load(), lineno=None, col_offset=None),
                    args=[],
                    keywords=[
                        keyword(
                            arg=None,
                            value=DictComp(
                                key=Name(
                                    id="attr", ctx=Load(), lineno=None, col_offset=None
                                ),
                                value=Call(
                                    func=Name(
                                        id="getattr",
                                        ctx=Load(),
                                        lineno=None,
                                        col_offset=None,
                                    ),
                                    args=[
                                        Name(
                                            id="record",
                                            ctx=Load(),
                                            lineno=None,
                                            col_offset=None,
                                        ),
                                        Name(
                                            id="attr",
                                            ctx=Load(),
                                            lineno=None,
                                            col_offset=None,
                                        ),
                                    ],
                                    keywords=[],
                                    lineno=None,
                                    col_offset=None,
                                ),
                                generators=[
                                    comprehension(
                                        target=Name(
                                            id="attr",
                                            ctx=Store(),
                                            lineno=None,
                                            col_offset=None,
                                        ),
                                        iter=Tuple(
                                            elts=list(
                                                map(
                                                    cdd.shared.ast_utils.set_value, keys
                                                )
                                            ),
                                            ctx=Load(),
                                            lineno=None,
                                            col_offset=None,
                                        ),
                                        ifs=[
                                            Compare(
                                                left=Call(
                                                    func=Name(
                                                        id="getattr",
                                                        ctx=Load(),
                                                        lineno=None,
                                                        col_offset=None,
                                                    ),
                                                    args=[
                                                        Name(
                                                            id="record",
                                                            ctx=Load(),
                                                            lineno=None,
                                                            col_offset=None,
                                                        ),
                                                        Name(
                                                            id="attr",
                                                            ctx=Load(),
                                                            lineno=None,
                                                            col_offset=None,
                                                        ),
                                                        cdd.shared.ast_utils.set_value(
                                                            None
                                                        ),
                                                    ],
                                                    keywords=[],
                                                    lineno=None,
                                                    col_offset=None,
                                                ),
                                                ops=[IsNot()],
                                                comparators=[
                                                    cdd.shared.ast_utils.set_value(None)
                                                ],
                                                lineno=None,
                                                col_offset=None,
                                            )
                                        ],
                                        is_async=0,
                                    )
                                ],
                                lineno=None,
                                col_offset=None,
                            ),
                            identifier=None,
                        )
                    ],
                    expr=None,
                    expr_func=None,
                    lineno=None,
                    col_offset=None,
                ),
                expr=None,
            ),
        ],
        decorator_list=[Name("staticmethod", Load(), lineno=None, col_offset=None)],
        arguments_args=None,
        identifier_name=None,
        stmt=None,
        lineno=None,
        returns=None,
        **cdd.shared.ast_utils.maybe_type_comment,
    )

generate_create_tables_mod

generate_create_tables_mod(module_name)

Generate the Base.metadata.create_all(engine) for SQLalchemy

Parameters:

Name Type Description Default
module_name str

Module to import from

required

Returns:

Name Type Description
return_type Module

Module with imports for SQLalchemy

Source code in cdd/sqlalchemy/utils/emit_utils.py
def generate_create_tables_mod(module_name):
    """
    Generate the `Base.metadata.create_all(engine)` for SQLalchemy

    :param module_name: Module to import from
    :type module_name: ```str```

    :return: Module with imports for SQLalchemy
    :rtype: ```Module```
    """
    return Module(
        body=[
            ImportFrom(
                module=module_name,
                names=list(
                    map(
                        lambda name: alias(
                            name=name,
                            asname=None,
                            identifier=None,
                            identifier_name=None,
                        ),
                        ("Base", "engine"),
                    )
                ),
                level=0,
            ),
            If(
                test=Compare(
                    left=Name(
                        "__name__",
                        Load(),
                        lineno=None,
                        col_offset=None,
                    ),
                    ops=[Eq()],
                    comparators=[cdd.shared.ast_utils.set_value("__main__")],
                ),
                body=[
                    Expr(
                        value=Call(
                            func=Name(
                                "print",
                                Load(),
                                lineno=None,
                                col_offset=None,
                            ),
                            args=[
                                cdd.shared.ast_utils.set_value(
                                    "Base.metadata.create_all for"
                                ),
                                Attribute(
                                    value=Name(
                                        id="engine",
                                        ctx=Load(),
                                        lineno=None,
                                        col_offset=None,
                                    ),
                                    attr="name",
                                    ctx=Load(),
                                    lineno=None,
                                    col_offset=None,
                                ),
                                cdd.shared.ast_utils.set_value("-> ("),
                                Call(
                                    func=Attribute(
                                        value=cdd.shared.ast_utils.set_value(", "),
                                        attr="join",
                                        ctx=Load(),
                                        lineno=None,
                                        col_offset=None,
                                    ),
                                    args=[
                                        Call(
                                            func=Attribute(
                                                value=Attribute(
                                                    value=Attribute(
                                                        value=Name(
                                                            id="Base", ctx=Load()
                                                        ),
                                                        attr="metadata",
                                                        ctx=Load(),
                                                        lineno=None,
                                                        col_offset=None,
                                                    ),
                                                    attr="tables",
                                                    ctx=Load(),
                                                    lineno=None,
                                                    col_offset=None,
                                                ),
                                                attr="keys",
                                                ctx=Load(),
                                                lineno=None,
                                                col_offset=None,
                                            ),
                                            args=[],
                                            keywords=[],
                                        )
                                    ],
                                    keywords=[],
                                ),
                                cdd.shared.ast_utils.set_value(") ;"),
                            ],
                            keywords=[],
                        )
                    ),
                    Expr(
                        value=Call(
                            func=Attribute(
                                value=Attribute(
                                    value=Name(
                                        "Base",
                                        Load(),
                                        lineno=None,
                                        col_offset=None,
                                    ),
                                    attr="metadata",
                                    ctx=Load(),
                                    lineno=None,
                                    col_offset=None,
                                ),
                                attr="create_all",
                                ctx=Load(),
                                lineno=None,
                                col_offset=None,
                            ),
                            args=[
                                Name(
                                    "engine",
                                    Load(),
                                    lineno=None,
                                    col_offset=None,
                                )
                            ],
                            keywords=[],
                        )
                    ),
                ],
                orelse=[],
            ),
        ],
        type_ignores=[],
    )

generate_repr_method

generate_repr_method(params, cls_name, docstring_format, hybrid=False)

Generate a __repr__ method with all params, using str.format syntax

Parameters:

Name Type Description Default
params OrderedDict

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

required
cls_name str

Name of class

required
docstring_format Literal['rest', 'numpydoc', 'google']

Format of docstring

required
hybrid bool
required

Returns:

Name Type Description
return_type FunctionDef

__repr__ method

Source code in cdd/sqlalchemy/utils/emit_utils.py
def generate_repr_method(params, cls_name, docstring_format, hybrid=False):
    """
    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']```

    :param hybrid:
    :type hybrid: ```bool```

    :return: `__repr__` method
    :rtype: ```FunctionDef```
    """
    keys = tuple(params.keys())  # type: tuple[str, ...]
    return FunctionDef(
        name="__repr__",
        args=arguments(
            posonlyargs=[],
            arg=None,
            args=[cdd.shared.ast_utils.set_arg("self")],
            kwonlyargs=[],
            kw_defaults=[],
            defaults=[],
            vararg=None,
            kwarg=None,
        ),
        body=[
            Expr(
                cdd.shared.ast_utils.set_value(
                    """\n{sep}{_repr_docstring}""".format(
                        sep=tab * 2,
                        _repr_docstring=(
                            docstring_repr_str
                            if docstring_format == "rest"
                            else docstring_repr_google_str
                        ).lstrip(),
                    )
                ),
                lineno=None,
                col_offset=None,
            ),
            Return(
                value=Call(
                    func=Attribute(
                        cdd.shared.ast_utils.set_value(
                            "{cls_name}({format_args})".format(
                                cls_name=cls_name,
                                format_args=", ".join(
                                    map("{0}={{{0}!r}}".format, keys)
                                ),
                            )
                        ),
                        "format",
                        Load(),
                        lineno=None,
                        col_offset=None,
                    ),
                    args=[],
                    keywords=list(
                        map(
                            lambda key: ast.keyword(
                                arg=key,
                                value=(
                                    Attribute(
                                        value=Attribute(
                                            value=Attribute(
                                                value=Name(id="self", ctx=Load()),
                                                attr="__table__",
                                                ctx=Load(),
                                                lineno=None,
                                                col_offset=None,
                                            ),
                                            attr="c",
                                            ctx=Load(),
                                            lineno=None,
                                            col_offset=None,
                                        ),
                                        attr=key,
                                        ctx=Load(),
                                        lineno=None,
                                        col_offset=None,
                                    )
                                    if hybrid
                                    else Attribute(
                                        Name(
                                            "self", Load(), lineno=None, col_offset=None
                                        ),
                                        key,
                                        Load(),
                                        lineno=None,
                                        col_offset=None,
                                    )
                                ),
                                identifier=None,
                            ),
                            keys,
                        )
                    ),
                    expr=None,
                    expr_func=None,
                    lineno=None,
                    col_offset=None,
                ),
                expr=None,
            ),
        ],
        decorator_list=[],
        type_params=[],
        arguments_args=None,
        identifier_name=None,
        stmt=None,
        lineno=None,
        returns=None,
        **cdd.shared.ast_utils.maybe_type_comment,
    )

param_to_sqlalchemy_column_calls

param_to_sqlalchemy_column_calls(name_param, include_name)

Turn a param into Column(…)s

Parameters:

Name Type Description Default
name_param dict

Name, dict with keys: 'typ', 'doc', 'default'

required
include_name bool

Whether to include the name (exclude in declarative base)

required

Returns:

Name Type Description
return_type Iterable[Call]

Iterable of elements in form of: Column(…)

Source code in cdd/sqlalchemy/utils/emit_utils.py
def param_to_sqlalchemy_column_calls(name_param, include_name):
    """
    Turn a param into `Column(…)`s

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

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

    :return: Iterable of elements in form of: `Column(…)`
    :rtype: ```Iterable[Call]```
    """
    if system() == "Darwin":
        print("param_to_sqlalchemy_column_calls::include_name:", include_name, ";")
    name, _param = name_param
    del name_param

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

    nullable, x_typ_sql = _handle_column_args(
        _param, args, include_name, name, nullable
    )

    default = x_typ_sql.get("default", _param.get("default", ast))
    has_default: bool = default is not ast
    pk: bool = _param.get("doc", "").startswith("[PK]")
    fk: bool = _param.get("doc", "").startswith("[FK")
    if pk:
        _param["doc"] = _param["doc"][4:].lstrip()
        keywords.append(
            ast.keyword(
                arg="primary_key",
                value=cdd.shared.ast_utils.set_value(True),
                identifier=None,
            ),
        )
    elif fk:
        end: int = _param["doc"].find("]") + 1
        fk_val: str = _param["doc"][len("[FK(") : end - len(")]")]
        _param["doc"] = _param["doc"][end:].lstrip()
        args.append(
            Call(
                func=Name("ForeignKey", Load(), lineno=None, col_offset=None),
                args=[cdd.shared.ast_utils.set_value(fk_val)],
                keywords=[],
                lineno=None,
                col_offset=None,
            )
        )
    elif has_default and default not in none_types:
        nullable: bool = False

    keywords = _handle_column_keywords(
        _param, default, has_default, keywords, nullable, x_typ_sql
    )

    # elif _param["doc"]:
    #     keywords.append(
    #         ast.keyword(arg="comment", value=set_value(_param["doc"]), identifier=None)
    #     )

    # TODO:
    # if multiple:
    #     Call(
    #         func=Name("Column", Load(), lineno=None, col_offset=None),
    #         args=args,
    #         keywords=sorted(keywords, key=attrgetter("arg")),
    #         expr=None,
    #         expr_func=None,
    #         lineno=None,
    #         col_offset=None,
    #     )
    return (
        Call(
            func=Name("Column", Load(), lineno=None, col_offset=None),
            args=args,
            keywords=sorted(keywords, key=attrgetter("arg")),
            expr=None,
            expr_func=None,
            lineno=None,
            col_offset=None,
        ),
    )

rewrite_fk

rewrite_fk(symbol_to_module, column_assign)

Rewrite of the form:

column_name = Column(
    TableName0,
    ForeignKey("TableName0"),
    nullable=True,
)
To the following, inferring that the primary key field is id by resolving the symbol and ast.parseing it:
column_name = Column(Integer, ForeignKey("table_name0.id"))

Parameters:

Name Type Description Default
symbol_to_module Dict[str,str]`

Dictionary of symbol to module, like {"join": "os.path"}

required
column_assign Assign

column_name = Column() in SQLalchemy with unresolved foreign key

required

Returns:

Name Type Description
return_type Assign

Assign() in SQLalchemy with resolved foreign key

Source code in cdd/sqlalchemy/utils/emit_utils.py
def rewrite_fk(symbol_to_module, column_assign):
    """
    Rewrite of the form:
    ```py
    column_name = Column(
        TableName0,
        ForeignKey("TableName0"),
        nullable=True,
    )
    ```
    To the following, inferring that the primary key field is `id` by resolving the symbol and `ast.parse`ing it:
    ```py
    column_name = Column(Integer, ForeignKey("table_name0.id"))
    ```

    :param symbol_to_module: Dictionary of symbol to module, like `{"join": "os.path"}`
    :type symbol_to_module: ```Dict[str,str]````

    :param column_assign: `column_name = Column()` in SQLalchemy with unresolved foreign key
    :type column_assign: ```Assign```

    :return: `Assign()` in SQLalchemy with resolved foreign key
    :rtype: ```Assign```
    """
    assert (
        isinstance(column_assign.value, Call)
        and isinstance(column_assign.value.func, Name)
        and column_assign.value.func.id == "Column"
    ), 'Expected `Call.func.Name.id` of "<var> = Column" eval to `<var> = Column(...)` got `{code}`'.format(
        code=cdd.shared.source_transformer.to_code(column_assign).rstrip()
    )

    def rewrite_fk_from_import(column_name, foreign_key_call):
        """
        :param column_name: Field name
        :type column_name: ```Name```

        :param foreign_key_call: `ForeignKey` function call
        :type foreign_key_call: ```Call```

        :return:
        :rtype: ```tuple[Name, Call]```
        """
        assert isinstance(
            column_name, Name
        ), "Expected `Name` got `{type_name}`".format(
            type_name=type(column_name).__name__
        )
        assert (
            isinstance(foreign_key_call, Call)
            and isinstance(foreign_key_call.func, Name)
            and foreign_key_call.func.id == "ForeignKey"
        ), 'Expected `Call.func.Name.id` of "ForeignKey" eval to `ForeignKey(...)` got `{code}`'.format(
            code=cdd.shared.source_transformer.to_code(foreign_key_call).rstrip()
        )
        if column_name.id in symbol_to_module:
            with open(
                find_module_filepath(symbol_to_module[column_name.id], column_name.id),
                "rt",
            ) as f:
                mod: Module = ast.parse(f.read())
            matching_class: ClassDef = next(
                filter(
                    lambda node: isinstance(node, ClassDef)
                    and node.name == column_name.id,
                    mod.body,
                )
            )
            pk_typ = get_pk_and_type(matching_class)  # type: tuple[str, str]
            assert pk_typ is not None
            pk, typ = pk_typ
            del pk_typ
            return Name(typ, Load(), lineno=None, col_offset=None), Call(
                func=Name("ForeignKey", Load(), lineno=None, col_offset=None),
                args=[
                    cdd.shared.ast_utils.set_value(
                        ".".join((get_table_name(matching_class), pk))
                    )
                ],
                keywords=[],
                lineno=None,
                col_offset=None,
            )
        return column_name, foreign_key_call

    column_assign.value.args = list(
        chain.from_iterable(
            (
                rewrite_fk_from_import(*column_assign.value.args[:2]),
                column_assign.value.args[2:],
            )
        )
    )

    return column_assign

sqlalchemy_class_to_table

sqlalchemy_class_to_table(class_def, parse_original_whitespace)

Convert SQLalchemy class to SQLalchemy Table expression

Parameters:

Name Type Description Default
class_def ClassDef

A class inheriting from declarative Base, where Base = sqlalchemy.orm.declarative_base()

required
parse_original_whitespace bool

Whether to parse original whitespace or strip it out

required

Returns:

Name Type Description
return_type Call

SQLalchemy Table expression

Source code in cdd/sqlalchemy/utils/emit_utils.py
def sqlalchemy_class_to_table(class_def, parse_original_whitespace):
    """
    Convert SQLalchemy class to SQLalchemy Table expression

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

    :param parse_original_whitespace: Whether to parse original whitespace or strip it out
    :type parse_original_whitespace: ```bool```

    :return: SQLalchemy `Table` expression
    :rtype: ```Call```
    """
    assert isinstance(
        class_def, ClassDef
    ), "Expected `ClassDef` got `{type_name}`".format(
        type_name=type(class_def).__name__
    )

    # Hybrid SQLalchemy class/table handler
    table_dunder: Optional[Call] = next(
        filter(
            lambda assign: any(
                filter(
                    partial(eq, "__table__"),
                    map(attrgetter("id"), assign.targets),
                )
            ),
            filter(rpartial(isinstance, Assign), class_def.body),
        ),
        None,
    )
    if table_dunder is not None:
        return table_dunder

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

    name: str = cdd.shared.ast_utils.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: Optional[str] = ast.get_docstring(
        class_def, clean=parse_original_whitespace
    )

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

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

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

    return Call(
        func=Name("Table", Load(), lineno=None, col_offset=None),
        args=list(
            chain.from_iterable(
                (
                    (
                        cdd.shared.ast_utils.set_value(name),
                        Name("metadata_obj", 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=list(
            chain.from_iterable(
                (
                    (
                        iter(())
                        if doc_string is None
                        else (
                            keyword(
                                arg="comment",
                                value=cdd.shared.ast_utils.set_value(doc_string),
                                identifier=None,
                            ),
                        )
                    ),
                    (
                        keyword(
                            arg="keep_existing",
                            value=cdd.shared.ast_utils.set_value(True),
                            identifier=None,
                            expr=None,
                            lineno=None,
                            **cdd.shared.ast_utils.maybe_type_comment,
                        ),
                    ),
                )
            )
        ),
        expr=None,
        expr_func=None,
        lineno=None,
        col_offset=None,
    )

sqlalchemy_table_to_class

sqlalchemy_table_to_class(table_expr_ass)

Convert table_name = Table(column_name) to `class table_name(Base): column_name

Parameters:

Name Type Description Default
Source code in cdd/sqlalchemy/utils/emit_utils.py
def sqlalchemy_table_to_class(table_expr_ass):
    """Convert `table_name = Table(column_name)` to `class table_name(Base): column_name"""
    assert isinstance(
        table_expr_ass, Assign
    ), "Expected `Assign` got `{type_name}`".format(
        type_name=type(table_expr_ass).__name__
    )
    assert len(table_expr_ass.targets) == 1 and isinstance(
        table_expr_ass.targets[0], Name
    )
    assert len(table_expr_ass.value.args) > 1

    return ClassDef(
        name=table_expr_ass.targets[0].id,
        bases=[Name("Base", Load(), lineno=None, col_offset=None)],
        keywords=[],
        body=list(
            chain.from_iterable(
                (
                    (
                        Assign(
                            targets=[
                                Name(
                                    "__tablename__",
                                    Store(),
                                    lineno=None,
                                    col_offset=None,
                                )
                            ],
                            value=cdd.shared.ast_utils.set_value(
                                cdd.shared.ast_utils.get_value(
                                    table_expr_ass.value.args[0]
                                )
                            ),
                            expr=None,
                            lineno=None,
                            **cdd.shared.ast_utils.maybe_type_comment,
                        ),
                    ),
                    map(
                        lambda column_call: Assign(
                            targets=[
                                Name(
                                    cdd.shared.ast_utils.get_value(column_call.args[0]),
                                    Store(),
                                    lineno=None,
                                    col_offset=None,
                                )
                            ],
                            value=Call(
                                func=column_call.func,
                                args=(
                                    column_call.args[1:]
                                    if len(column_call.args) > 1
                                    else []
                                ),
                                keywords=column_call.keywords,
                                expr=None,
                                expr_func=None,
                            ),
                            expr=None,
                            lineno=None,
                            **cdd.shared.ast_utils.maybe_type_comment,
                        ),
                        filter(
                            lambda node: isinstance(node, Call)
                            and isinstance(node.func, Name)
                            and node.func.id == "Column",
                            table_expr_ass.value.args[2:],
                        ),
                    ),
                )
            )
        ),
        decorator_list=[],
        type_params=[],
        expr=None,
        lineno=None,
        col_offset=None,
        end_lineno=None,
        end_col_offset=None,
        identifier_name=None,
    )

update_fk_for_file

update_fk_for_file(filename)

Given an existing filename, use its imports and to replace its foreign keys with the correct values

This is subsequent phase process, and must be preceded by: - All SQLalchemy models being in the same directory as filename - Correct imports being added

Then it can transform classes with members like:

Column(
        TableName0,
        ForeignKey("TableName0"),
        nullable=True,
    )
To the following, inferring that the primary key field is id by resolving the symbol and ast.parseing it:
Column(Integer, ForeignKey("table_name0.id"))

Parameters:

Name Type Description Default
filename str

Filename

required
Source code in cdd/sqlalchemy/utils/emit_utils.py
def update_fk_for_file(filename):
    """
    Given an existing filename, use its imports and to replace its foreign keys with the correct values

    This is subsequent phase process, and must be preceded by:
    - All SQLalchemy models being in the same directory as filename
    - Correct imports being added

    Then it can transform classes with members like:
    ```py
    Column(
            TableName0,
            ForeignKey("TableName0"),
            nullable=True,
        )
    ```
    To the following, inferring that the primary key field is `id` by resolving the symbol and `ast.parse`ing it:
    ```py
    Column(Integer, ForeignKey("table_name0.id"))
    ```

    :param filename: Filename
    :type filename: ```str```
    """
    with open(filename, "rt") as f:
        mod: Module = ast.parse(f.read())

    def handle_sqlalchemy_cls(symbol_to_module, sqlalchemy_class_def):
        """
        Ensure the SQLalchemy classes have their foreign keys resolved properly

        :param symbol_to_module: Dictionary of symbol to module, like `{"join": "os.path"}`
        :type symbol_to_module: ```Dict[str,str]````

        :param sqlalchemy_class_def: SQLalchemy `class`
        :type sqlalchemy_class_def: ```ClassDef```

        :return: SQLalchemy with foreign keys resolved properly
        :rtype: ```ClassDef```
        """
        sqlalchemy_class_def.body = list(
            map(
                lambda outer_node: (
                    rewrite_fk(symbol_to_module, outer_node)
                    if isinstance(outer_node, Assign)
                    and isinstance(outer_node.value, Call)
                    and isinstance(outer_node.value.func, Name)
                    and outer_node.value.func.id == "Column"
                    and any(
                        filter(
                            lambda node: isinstance(node, Call)
                            and isinstance(node.func, Name)
                            and node.func.id == "ForeignKey",
                            outer_node.value.args,
                        )
                    )
                    else outer_node
                ),
                sqlalchemy_class_def.body,
            )
        )
        return sqlalchemy_class_def

    symbol2module: Dict[str, Any] = dict(
        chain.from_iterable(
            map(
                lambda import_from: map(
                    lambda _alias: (_alias.name, import_from.module), import_from.names
                ),
                filterfalse(
                    lambda import_from: import_from.module == "sqlalchemy",
                    filter(
                        rpartial(isinstance, ImportFrom),
                        ast.walk(mod),
                    ),
                ),
            )
        )
    )

    mod.body = list(
        map(
            lambda node: (
                handle_sqlalchemy_cls(symbol2module, node)
                if isinstance(node, ClassDef)
                and any(
                    filter(
                        lambda base: isinstance(base, Name) and base.id == "Base",
                        node.bases,
                    )
                )
                else node
            ),
            mod.body,
        )
    )

    with open(filename, "wt") as f:
        f.write(cdd.shared.source_transformer.to_code(mod))

update_with_imports_from_columns

update_with_imports_from_columns(filename)

Given an existing filename, figure out its relative imports

This is subsequent phase process, and must be preceded by: - All SQLalchemy models being in the same directory as filename

It will take:

Column(TableName0,
       ForeignKey("TableName0"),
       nullable=True)
…and add this import:
from `basename(filename)`.table_name import TableName0

Parameters:

Name Type Description Default
filename str

Python filename containing SQLalchemy class(es)

required
Source code in cdd/sqlalchemy/utils/emit_utils.py
def update_with_imports_from_columns(filename):
    """
    Given an existing filename, figure out its relative imports

    This is subsequent phase process, and must be preceded by:
    - All SQLalchemy models being in the same directory as filename

    It will take:
    ```py
    Column(TableName0,
           ForeignKey("TableName0"),
           nullable=True)
    ```
    …and add this import:
    ```py
    from `basename(filename)`.table_name import TableName0
    ```

    :param filename: Python filename containing SQLalchemy `class`(es)
    :type filename: ```str```
    """
    with open(filename, "rt") as f:
        mod: Module = ast.parse(f.read())

    candidates = sorted(
        frozenset(
            filter(
                str.istitle,
                filterfalse(
                    frozenset(
                        ("complex", "float", "int", "list", "long", "self", "string")
                    ).__contains__,
                    filterfalse(
                        sqlalchemy_top_level_imports.__contains__,
                        map(
                            attrgetter("id"),
                            filter(
                                rpartial(isinstance, Name),
                                ast.walk(
                                    Module(
                                        body=list(
                                            filter(
                                                rpartial(isinstance, Call),
                                                ast.walk(mod),
                                            )
                                        ),
                                        type_ignores=[],
                                        stmt=None,
                                    )
                                ),
                            ),
                        ),
                    ),
                ),
            )
        )
    )

    module: str = path.basename(path.dirname(filename))
    mod.body = list(
        chain.from_iterable(
            (
                map(
                    lambda class_name: ImportFrom(
                        module=".".join(
                            (module, namespaced_upper_camelcase_to_pascal(class_name))
                        ),
                        names=[
                            alias(
                                class_name,
                                None,
                                identifier=None,
                                identifier_name=None,
                            )
                        ],
                        level=0,
                    ),
                    candidates,
                ),
                mod.body,
            )
        )
    )

    with open(filename, "wt") as f:
        f.write(cdd.shared.source_transformer.to_code(mod))

cdd.sqlalchemy.utils.parse_utils

cdd.sqlalchemy.utils.parse_utils

Utility functions for cdd.parse.sqlalchemy

Parameters:

Name Type Description Default

column_call_name_manipulator

column_call_name_manipulator(call, operation='remove', name=None)

Parameters:

Name Type Description Default
call Call``

Column function call within SQLalchemy

required
operation Literal["remove", "add"]
required
name str
required

Returns:

Name Type Description
return_type Call

Column call

Source code in cdd/sqlalchemy/utils/parse_utils.py
def column_call_name_manipulator(call, operation="remove", name=None):
    """
    :param call: `Column` function call within SQLalchemy
    :type call: ```Call``

    :param operation:
    :type operation: ```Literal["remove", "add"]```

    :param name:
    :type name: ```str```

    :return: Column call
    :rtype: ```Call```
    """
    assert (
        isinstance(call, Call)
        and isinstance(call.func, Name)
        and call.func.id == "Column"
    )
    if isinstance(call.args[0], (Constant, Str)) and operation == "remove":
        del call.args[0]
    elif operation == "add" and name is not None:
        call.args.insert(0, Name(name, Load()))
    return call

column_call_to_param

column_call_to_param(call)

Parse column call Call(func=Name("Column", Load(), …) into param

Parameters:

Name Type Description Default
call Call

Column call from SQLAlchemy Table construction

required

Returns:

Name Type Description
return_type tuple[str, dict]

Name, dict with keys: 'typ', 'doc', 'default'

Source code in cdd/sqlalchemy/utils/parse_utils.py
def column_call_to_param(call):
    """
    Parse column call `Call(func=Name("Column", Load(), …)` into param

    :param call: Column call from SQLAlchemy `Table` construction
    :type call: ```Call```

    :return: Name, dict with keys: 'typ', 'doc', 'default'
    :rtype: ```tuple[str, dict]```
    """
    assert call.func.id == "Column", "{} != Column".format(call.func.id)
    assert (
        len(call.args) < 4
    ), "Complex column parsing not implemented for: Column({})".format(
        ", ".join(map(repr, map(cdd.shared.ast_utils.get_value, call.args)))
    )

    _param = dict(
        filter(
            None,
            chain.from_iterable(
                (
                    map(column_parse_arg, enumerate(call.args)),
                    map(column_parse_extra_sql, enumerate(call.args)),
                    map(column_parse_kwarg, call.keywords),
                )
            ),
        )
    )
    if "comment" in _param and "doc" not in _param:
        _param["doc"] = _param.pop("comment")

    if "server_default" in _param:
        append_to_dict(
            _param,
            ["x_typ", "sql", "constraints", "server_default"],
            _param["server_default"],
        )

    for shortname, longname in ("PK", "primary_key"), (
        "FK({})".format(_param.get("foreign_key")),
        "foreign_key",
    ):
        if longname in _param:
            _param["doc"] = (
                "[{}] {}".format(shortname, _param["doc"])
                if _param.get("doc")
                else "[{}]".format(shortname)
            )
            del _param[longname]

    def _handle_null():
        """
        Properly handle null condition
        """
        if not _param["typ"].startswith("Optional["):
            _param["typ"] = "Optional[{}]".format(_param["typ"])

    if "nullable" in _param:
        not _param["nullable"] or _handle_null()
        del _param["nullable"]

    if (
        "default" in _param
        and not cdd.shared.ast_utils.get_value(call.args[0]).endswith("kwargs")
        and "doc" in _param
    ):
        _param["doc"] += "."

    return cdd.shared.ast_utils.get_value(call.args[0]), _param

column_parse_arg

column_parse_arg(idx_arg)

Parse Column arg

Parameters:

Name Type Description Default
idx_arg int

argument number, node

required

Returns:

Name Type Description
return_type Optional[Tuple[str, AST]]
Source code in cdd/sqlalchemy/utils/parse_utils.py
def column_parse_arg(idx_arg):
    """
    Parse Column arg

    :param idx_arg: argument number, node
    :type idx_arg: ```tuple[int, AST]```

    :rtype: ```Optional[Tuple[str, AST]]```
    """
    idx, arg = idx_arg
    if idx < 2 and isinstance(arg, Name):
        return "typ", column_type2typ.get(arg.id, arg.id)
    elif isinstance(arg, Call):
        func_id = arg.func.id.rpartition(".")[2]
        if func_id == "Enum":
            return "typ", "Literal{}".format(
                list(map(cdd.shared.ast_utils.get_value, arg.args))
            )
        elif func_id == "ForeignKey":
            return "foreign_key", ",".join(
                map(cdd.shared.ast_utils.get_value, arg.args)
            )
        else:
            return "typ", cdd.shared.source_transformer.to_code(idx_arg[1]).replace(
                "\n", ""
            )

    val = cdd.shared.ast_utils.get_value(arg)
    assert val != arg, "Unable to parse {!r}".format(arg)
    return None if idx == 0 else (None, val)

column_parse_extra_sql

column_parse_extra_sql(idx_arg)

Parse Column arg into extra sql type information

Parameters:

Name Type Description Default
idx_arg int

argument number, node

required

Returns:

Name Type Description
return_type Optional[Tuple[str, dict]]
Source code in cdd/sqlalchemy/utils/parse_utils.py
def column_parse_extra_sql(idx_arg):
    """
    Parse Column arg into extra sql type information

    :param idx_arg: argument number, node
    :type idx_arg: ```tuple[int, AST]```

    :rtype: ```Optional[Tuple[str, dict]]```
    """
    idx, arg = idx_arg
    if idx < 2 and isinstance(arg, Name) and arg.id in column_type2typ:
        return "x_typ", {"sql": {"type": arg.id}}
    return None

column_parse_kwarg

column_parse_kwarg(key_word)

Parse Column kwarg

Parameters:

Name Type Description Default
key_word ast.keyword

The keyword argument

required

Returns:

Name Type Description
return_type tuple[str, Any]
Source code in cdd/sqlalchemy/utils/parse_utils.py
def column_parse_kwarg(key_word):
    """
    Parse Column kwarg

    :param key_word: The keyword argument
    :type key_word: ```ast.keyword```

    :rtype: ```tuple[str, Any]```
    """
    val = cdd.shared.ast_utils.get_value(key_word.value)

    # Checking that the keyword.value has a value OR is a function call.
    assert val != key_word.value or isinstance(
        key_word.value, Call
    ), "Unable to parse {!r} of {}".format(
        key_word.arg, cdd.shared.source_transformer.to_code(key_word.value)
    )
    return key_word.arg, val

concat_with_whitespace

concat_with_whitespace(a, b)

Concatenate a with b with correct whitespace around and within each

Parameters:

Name Type Description Default
a str

first string

required
b str

second string

required

Returns:

Name Type Description
return_type str

combined strings with correct whitespace around and within

Source code in cdd/sqlalchemy/utils/parse_utils.py
def concat_with_whitespace(a, b):
    """
    Concatenate a with b with correct whitespace around and within each

    :param a: first string
    :type a: ```str```

    :param b: second string
    :type b: ```str```

    :return: combined strings with correct whitespace around and within
    :rtype: ```str```
    """
    b_splits = b.split("\n{tab}".format(tab=tab))
    res = "{a}{snd}\n{tab}{end}{tab}".format(
        a=a, tab=tab, snd=b_splits[0], end="\n".join(b_splits[1:])
    )
    return indent_all_but_first(res, indent_level=1, sep=tab)

get_pk_and_type

get_pk_and_type(sqlalchemy_class)

Get the primary key and its type from an SQLalchemy class

Parameters:

Name Type Description Default
sqlalchemy_class ClassDef

SQLalchemy class

required

Returns:

Name Type Description
return_type tuple[str, str]

Primary key and its type

Source code in cdd/sqlalchemy/utils/parse_utils.py
def get_pk_and_type(sqlalchemy_class):
    """
    Get the primary key and its type from an SQLalchemy class

    :param sqlalchemy_class: SQLalchemy class
    :type sqlalchemy_class: ```ClassDef```

    :return: Primary key and its type
    :rtype: ```tuple[str, str]```
    """
    assert isinstance(
        sqlalchemy_class, ClassDef
    ), "Expected `ClassDef` got `{type_name}`".format(
        type_name=type(sqlalchemy_class).__name__
    )
    return (
        lambda assign: (
            assign
            if assign is None
            else (
                assign.targets[0].id,
                assign.value.args[0].id,  # First arg is type
            )
        )
    )(
        next(
            filter(
                lambda assign: any(
                    filter(
                        lambda key_word: key_word.arg == "primary_key"
                        and cdd.shared.ast_utils.get_value(key_word.value) is True,
                        assign.value.keywords,
                    )
                ),
                filter(
                    lambda assign: isinstance(assign.value, Call)
                    and isinstance(assign.value.func, Name)
                    and assign.value.func.id == "Column",
                    filter(rpartial(isinstance, Assign), sqlalchemy_class.body),
                ),
            ),
            None,
        )
    )

get_table_name

get_table_name(sqlalchemy_class)

Get the primary key and its type from an SQLalchemy class

Parameters:

Name Type Description Default
sqlalchemy_class ClassDef

SQLalchemy class

required

Returns:

Name Type Description
return_type str

Primary key and its type

Source code in cdd/sqlalchemy/utils/parse_utils.py
def get_table_name(sqlalchemy_class):
    """
    Get the primary key and its type from an SQLalchemy class

    :param sqlalchemy_class: SQLalchemy class
    :type sqlalchemy_class: ```ClassDef```

    :return: Primary key and its type
    :rtype: ```str```
    """
    return next(
        map(
            lambda assign: cdd.shared.ast_utils.get_value(assign.value),
            filter(
                lambda node: next(
                    filter(lambda target: target.id == "__tablename__", node.targets),
                    None,
                )
                and node,
                filter(
                    lambda node: isinstance(node, Assign)
                    and isinstance(node.value, (Str, Constant)),
                    sqlalchemy_class.body,
                ),
            ),
        ),
        sqlalchemy_class.name,
    )

cdd.sqlalchemy.utils.shared_utils

cdd.sqlalchemy.utils.shared_utils

Shared utility functions for SQLalchemy

Parameters:

Name Type Description Default

update_args_infer_typ_sqlalchemy

update_args_infer_typ_sqlalchemy(_param, args, name, nullable, x_typ_sql)

Parameters:

Name Type Description Default
_param dict

Param with typ

required
args list
required
name str
required
nullable Optional[bool]

Whether it is NULL-able

required
x_typ_sql dict
required

Returns:

Name Type Description
return_type Tuple[bool, Optional[Union[List[AST], Tuple[AST]]]]

Whether the type is nullable, possibly a list/tuple of types to generate columns for

Source code in cdd/sqlalchemy/utils/shared_utils.py
def update_args_infer_typ_sqlalchemy(_param, args, name, nullable, x_typ_sql):
    """
    :param _param: Param with typ
    :type _param: ```dict```

    :param args:
    :type args: ```list```

    :param name:
    :type name: ```str```

    :param nullable: Whether it is NULL-able
    :type nullable: ```Optional[bool]```

    :param x_typ_sql:
    :type x_typ_sql: ```dict```

    :return: Whether the type is nullable, possibly a list/tuple of types to generate columns for
    :rtype: ```Tuple[bool, Optional[Union[List[AST], Tuple[AST]]]]```
    """
    if _param["typ"] is None:
        return _param.get("default") == cdd.shared.ast_utils.NoneStr, None
    elif _param["typ"].startswith("Optional["):
        _param["typ"] = _param["typ"][len("Optional[") : -1]
        nullable: bool = True
    if "Literal[" in _param["typ"]:
        parsed_typ: Call = cast(
            Call, cdd.shared.ast_utils.get_value(ast.parse(_param["typ"]).body[0])
        )
        assert parsed_typ.value.id == "Literal", "Expected `Literal` got: {!r}".format(
            parsed_typ.value.id
        )
        val = cdd.shared.ast_utils.get_value(parsed_typ.slice)
        (
            args.append(
                Call(
                    func=Name("Enum", Load(), lineno=None, col_offset=None),
                    args=val.elts,
                    keywords=[
                        ast.keyword(
                            arg="name",
                            value=cdd.shared.ast_utils.set_value(name),
                            identifier=None,
                        )
                    ],
                    expr=None,
                    expr_func=None,
                    lineno=None,
                    col_offset=None,
                )
            )
            if hasattr(val, "elts")
            else _update_args_infer_typ_sqlalchemy_for_scalar(_param, args, x_typ_sql)
        )
    elif _param["typ"].startswith("List["):
        after_generic: str = _param["typ"][len("List[") :]
        if "struct" in after_generic:  # "," in after_generic or
            name: Name = Name(id="JSON", ctx=Load(), lineno=None, col_offset=None)
        else:
            list_typ: Expr = cast(Expr, ast.parse(_param["typ"]).body[0])
            assert isinstance(
                list_typ, Expr
            ), "Expected `Expr` got `{type_name}`".format(
                type_name=type(list_typ).__name__
            )
            assert isinstance(
                list_typ.value, Subscript
            ), "Expected `Subscript` got `{type_name}`".format(
                type_name=type(list_typ.value).__name__
            )
            name: Optional[Name] = next(
                filter(rpartial(isinstance, Name), ast.walk(list_typ.value.slice)), None
            )
            assert name is not None, "Could not find a type in {!r}".format(
                cdd.shared.source_transformer.to_code(list_typ.value.slice)
            )
        args.append(
            Call(
                func=Name(id="ARRAY", ctx=Load(), lineno=None, col_offset=None),
                args=[
                    Name(
                        id=cdd.sqlalchemy.utils.emit_utils.typ2column_type.get(
                            name.id, name.id
                        ),
                        ctx=Load(),
                    )
                ],
                keywords=[],
                expr=None,
                expr_func=None,
                lineno=None,
                col_offset=None,
            )
        )
    elif (
        "items" in _param
        and _param["items"].get("type", False)
        in cdd.sqlalchemy.utils.emit_utils.typ2column_type
    ):
        args.append(
            Call(
                func=Name(id="ARRAY", ctx=Load(), lineno=None, col_offset=None),
                args=[
                    Name(
                        id=cdd.sqlalchemy.utils.emit_utils.typ2column_type[
                            _param["items"]["type"]
                        ],
                        ctx=Load(),
                    )
                ],
                keywords=[],
                expr=None,
                expr_func=None,
                lineno=None,
                col_offset=None,
            )
        )
    elif _param.get("typ").startswith("Union["):
        args.append(_handle_union_of_length_2(_param["typ"]))
    else:
        _update_args_infer_typ_sqlalchemy_for_scalar(_param, args, x_typ_sql)
    return nullable, None