pyluwen 0.5.2

Python bindings for luwen
Documentation
# Extremely heavily inspired by https://github.com/huggingface/tokenizers/blob/main/bindings/python/stub.py

import argparse
import inspect
from pathlib import Path

INDENT = " " * 4
GENERATED_COMMENT = "# Generated content DO NOT EDIT\n"

def do_indent(text: str, indent: str):
    return text.replace("\n", f"\n{indent}")

def function(obj, indent, text_signature=None):
    if text_signature is None:
        text_signature = obj.__text_signature__.replace("$self", "self")
    string = ""
    string += f"{indent}def {obj.__name__}{text_signature}:\n"
    indent += INDENT
    string += f'{indent}"""\n'
    if obj.__doc__ is not None and len(obj.__doc__) > 0:
        string += f"{indent}{do_indent(obj.__doc__, indent)}\n"
    string += f'{indent}"""\n'
    string += f"{indent}pass\n"
    string += "\n"
    return string

def member_sort(member):
    if inspect.isclass(member):
        value = 10 + len(inspect.getmro(member))
    else:
        value = 1
    return value

def fn_predicate(obj):
    value = inspect.ismethoddescriptor(obj) or inspect.isbuiltin(obj)
    if value:
        return obj.__text_signature__ and not obj.__name__.startswith("_")
    if inspect.isgetsetdescriptor(obj):
        return obj.__doc__ is not None and not obj.__name__.startswith("_")
    return False

def get_module_members(module):
    members = [
        member
        for name, member in inspect.getmembers(module)
        if not name.startswith("_") and not inspect.ismodule(member)
    ]
    members.sort(key=member_sort)
    return members


def pyi_file(obj, indent=""):
    string = ""
    if inspect.ismodule(obj):
        string += GENERATED_COMMENT
        members = get_module_members(obj)
        for member in members:
            string += pyi_file(member, indent)

    elif inspect.isclass(obj):
        indent += INDENT
        mro = inspect.getmro(obj)
        if len(mro) > 2:
            inherit = f"({mro[1].__name__})"
        else:
            inherit = ""
        string += f"class {obj.__name__}{inherit}:\n"

        body = ""
        if obj.__doc__ is not None and len(obj.__doc__) > 0:
            body += f'{indent}"""\n{indent}{do_indent(obj.__doc__, indent)}\n{indent}"""\n'

        fns = inspect.getmembers(obj, fn_predicate)

        # Init
        if obj.__text_signature__ is not None:
            sig = obj.__text_signature__
            if not sig.startswith("(self"):
                sig = sig.replace("(", "(self, ")
            body += f"{indent}def __init__{sig}:\n"
            body += f"{indent+INDENT}pass\n"
            body += "\n"

        for (name, fn) in fns:
            body += pyi_file(fn, indent=indent)

        if len(body) > 0:
            body += f"{indent}pass\n"

        string += body
        if not string.endswith("\n\n"):
            string += "\n"

    elif inspect.isbuiltin(obj):
        string += f"{indent}@staticmethod\n"
        string += function(obj, indent)

    elif inspect.ismethoddescriptor(obj):
        string += function(obj, indent)

    elif inspect.isgetsetdescriptor(obj):
        string += f"{indent}@property\n"
        string += function(obj, indent, text_signature="(self)")
    else:
        raise Exception(f"Object {obj} is not supported")
    return string


def py_file(module, origin):
    members = get_module_members(module)

    string = GENERATED_COMMENT
    string += f"from .. import {origin}\n"
    string += "\n"
    for member in members:
        name = member.__name__
        string += f"{name} = {origin}.{name}\n"
    return string


# def do_black(content, is_pyi):
#     mode = black.Mode(
#         target_versions={black.TargetVersion.PY35},
#         line_length=119,
#         is_pyi=is_pyi,
#         string_normalization=True,
#         experimental_string_processing=False,
#     )
#     try:
#         return black.format_file_contents(content, fast=True, mode=mode)
#     except black.NothingChanged:
#         return content


def write(module, pyi_filename, check=False):
    submodules = [(name, member) for name, member in inspect.getmembers(module) if inspect.ismodule(member)]

    pyi_content = pyi_file(module)
    with open(pyi_filename, "w") as f:
        f.write(pyi_content)

    assert len(submodules) == 0, "There are now submodules for pyluwen, you should extend this to support generating .pyi files for them"

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--check", action="store_true")

    args = parser.parse_args()
    from pyluwen import pyluwen

    write(pyluwen, Path(__file__).parent.joinpath("pyluwen.pyi"), check=args.check)

if __name__ == "__main__":
    main()