Esempio n. 1
0
def generate_pyi_from_file(file: Path) -> bool:
    """Generate a .pyi stubfile from a single .py module using mypy/stubgen"""

    sg_opt = stubgen.Options(
        pyversion=(3, 6),
        no_import=False,
        include_private=True,
        doc_dir="",
        search_path=[],
        interpreter=sys.executable,
        parse_only=False,
        ignore_errors=True,
        modules=[],
        packages=[],
        files=[],
        output_dir="",
        verbose=True,
        quiet=False,
        export_less=False,
    )
    # Deal with generator passed in
    if not isinstance(file, Path):
        raise TypeError
    sg_opt.files = [str(file)]
    sg_opt.output_dir = str(file.parent)
    try:
        print(f"Calling stubgen on {str(file)}")
        # WORKAROUND: Stubgen.generate_stubs does not provide a way to return the errors
        # such as `cannot perform relative import`
        # UPSTREAM: robust.py needs this to do a relative import
        if file.name == "robust.py" and file.parent.name == "umqtt":
            with open(file.with_name("__init__.py"), "a") as init:
                init.write("# force __init__.py")
        stubgen.generate_stubs(sg_opt)
        return True
    except (Exception, CompileError, SystemExit) as e:
        print(e)
        return False
Esempio n. 2
0
def generate_typed_stubs(modpath):
    """
    Attempt to use google-style docstrings, xdoctest, and mypy to generate
    typed stub files.

    Does not overwrite anything by itself.

    Args:
        modpath (PathLike): path to the module to generate types for

    Returns:
        Dict[PathLike, str]:
            A dictionary mapping the path of each file to write to the text to
            be written.

    Notes:
        FIXME: This currently requires my hacked version of mypy

    Example:
        >>> # xdoctest: +SKIP
        >>> # xdoctest: +REQUIRES(module:mypy)
        >>> # xdoctest: +REQUIRES(--hacked)
        >>> from xdev.cli.docstr_stubgen import *  # NOQA
        >>> import xdev
        >>> import ubelt as ub
        >>> from xdev.cli import docstr_stubgen
        >>> modpath = ub.Path(docstr_stubgen.__file__)
        >>> generated = generate_typed_stubs(modpath)
        >>> text = generated[ub.peek(generated.keys())]
        >>> assert 'PathLike' in text
        >>> assert 'Dict' in text
        >>> print(text)

    Ignore:
        pyfile mypy.stubgen
        # Delete compiled verisons so we can hack it

        # ls $VIRTUAL_ENV/lib/*/site-packages/mypy/*.so
        # rm $VIRTUAL_ENV/lib/*/site-packages/mypy/*.so
        # rm ~/.pyenv/versions/3.8.6/envs/pyenv3.8.6/lib/python3.8/site-packages/mypy/*.cpython-38-x86_64-linux-gnu.so

        # This works I think?
        if [[ ! -e "$HOME/code/mypy" ]];  then
            git clone https://github.com/python/mypy.git $HOME/code/mypy
        fi
        (cd $HOME/code/mypy && git pull)
        pip install -e $HOME/code/mypy


        pip install MonkeyType

        monkeytype run run_tests.py
        monkeytype stub ubelt.util_dict

        from typing import TypeVar
        from mypy.applytype import get_target_type
        z = TypeVar('Iterable')
        get_target_type(z)

        from mypy.expandtype import expand_type
        expand_type(z, env={})

        from mypy.types import get_proper_type
        get_proper_type(z)
        get_proper_type(dict)
        import typing
        get_proper_type(typing.Iterable)

        from mypy.types import deserialize_type, UnboundType
        import mypy.types as mypy_types
        z = UnboundType('Iterable')
        get_proper_type(dict)

        from mypy.fastparse import parse_type_string
        parse_type_string('dict', 'dict', 0, 0)
        z = parse_type_string('typing.Iterator', 'Any', 0, 0)
        get_proper_type(z)

    """
    # import pathlib
    # import ubelt as ub
    import os
    from mypy import stubgen
    from mypy import defaults
    from xdoctest import static_analysis
    from os.path import join
    import ubelt as ub

    # modname = 'scriptconfig'
    # module = ub.import_module_from_name(modname)
    # modpath = ub.Path(module.__file__).parent

    # for p in pathlib.Path(modpath).glob('*.pyi'):
    #     p.unlink()
    modpath = ub.Path(modpath)

    files = list(
        static_analysis.package_modpaths(modpath,
                                         recursive=True,
                                         with_libs=1,
                                         with_pkg=0))

    # files = [f for f in files if 'deprecated' not in f]
    # files = [join(ubelt_dpath, 'util_dict.py')]

    if modpath.is_file():
        output_dir = modpath.parent.parent
    else:
        output_dir = modpath.parent

    options = stubgen.Options(pyversion=defaults.PYTHON3_VERSION,
                              no_import=True,
                              doc_dir='',
                              search_path=[],
                              interpreter=sys.executable,
                              ignore_errors=False,
                              parse_only=True,
                              include_private=False,
                              output_dir=output_dir,
                              modules=[],
                              packages=[],
                              files=files,
                              verbose=False,
                              quiet=False,
                              export_less=True)
    # generate_stubs(options)

    mypy_opts = stubgen.mypy_options(options)
    py_modules, c_modules = stubgen.collect_build_targets(options, mypy_opts)

    # Collect info from docs (if given):
    sigs = class_sigs = None  # type: Optional[Dict[str, str]]
    if options.doc_dir:
        sigs, class_sigs = stubgen.collect_docs_signatures(options.doc_dir)

    # Use parsed sources to generate stubs for Python modules.
    stubgen.generate_asts_for_modules(py_modules, options.parse_only,
                                      mypy_opts, options.verbose)

    generated = {}

    for mod in py_modules:
        assert mod.path is not None, "Not found module was not skipped"
        target = mod.module.replace('.', '/')
        if os.path.basename(mod.path) == '__init__.py':
            target += '/__init__.pyi'
        else:
            target += '.pyi'
        target = join(options.output_dir, target)
        files.append(target)
        with stubgen.generate_guarded(mod.module, target,
                                      options.ignore_errors, options.verbose):
            stubgen.generate_stub_from_ast(mod, target, options.parse_only,
                                           options.pyversion,
                                           options.include_private,
                                           options.export_less)

            gen = ExtendedStubGenerator(
                mod.runtime_all,
                pyversion=options.pyversion,
                include_private=options.include_private,
                analyzed=not options.parse_only,
                export_less=options.export_less)
            assert mod.ast is not None, "This function must be used only with analyzed modules"
            mod.ast.accept(gen)
            # print('gen.import_tracker.required_names = {!r}'.format(gen.import_tracker.required_names))
            # print(gen.import_tracker.import_lines())

            # print('mod.path = {!r}'.format(mod.path))

            known_one_letter_types = {
                # 'T', 'K', 'A', 'B', 'C', 'V',
                'DT',
                'KT',
                'VT',
                'T'
            }
            for type_var_name in set(gen.import_tracker.required_names) & set(
                    known_one_letter_types):
                gen.add_typing_import('TypeVar')
                # gen.add_import_line('from typing import {}\n'.format('TypeVar'))
                gen._output = [
                    '{} = TypeVar("{}")\n'.format(type_var_name, type_var_name)
                ] + gen._output

            custom_types = {'Hasher'}
            for type_var_name in set(
                    gen.import_tracker.required_names) & set(custom_types):
                gen.add_typing_import('TypeVar')
                # gen.add_import_line('from typing import {}\n'.format('TypeVar'))
                gen._output = [
                    '{} = TypeVar("{}")\n'.format(type_var_name, type_var_name)
                ] + gen._output

            # Hack for specific module
            # if mod.path.endswith('util_path.py'):
            #     gen.add_typing_import('TypeVar')
            #     # hack for variable inheritence
            #     gen._output = ['import pathlib\nimport os\n', "_PathBase = pathlib.WindowsPath if os.name == 'nt' else pathlib.PosixPath\n"] + gen._output

            text = ''.join(gen.output())
            text = postprocess_hacks(text, mod)

            # Write output to file.
            # subdir = ub.Path(target).parent
            # if subdir and not os.path.isdir(subdir):
            #     os.makedirs(subdir)
            generated[target] = text
            # with open(target, 'w') as file:
            #     file.write(text)
    return generated
Esempio n. 3
0
def generate_typed_stubs():
    """
    Attempt to use google-style docstrings, xdoctest, and mypy to generate
    typed stub files.

    pyfile mypy.stubgen
    # Delete compiled verisons so we can hack it
    rm ~/.pyenv/versions/3.8.6/envs/pyenv3.8.6/lib/python3.8/site-packages/mypy/*.cpython-38-x86_64-linux-gnu.so

    git clone https://github.com/python/mypy.git
    cd mypy
    pip install -e .


    pip install MonkeyType

    monkeytype run run_tests.py
    monkeytype stub ubelt.util_dict

    from typing import TypeVar
    from mypy.applytype import get_target_type
    z = TypeVar('Iterable')
    get_target_type(z)

    from mypy.expandtype import expand_type
    expand_type(z, env={})

    from mypy.types import get_proper_type
    get_proper_type(z)
    get_proper_type(dict)
    import typing
    get_proper_type(typing.Iterable)

    from mypy.types import deserialize_type, UnboundType
    import mypy.types as mypy_types
    z = UnboundType('Iterable')
    get_proper_type(dict)

    from mypy.fastparse import parse_type_string
    parse_type_string('dict', 'dict', 0, 0)
    z = parse_type_string('typing.Iterator', 'Any', 0, 0)
    get_proper_type(z)

    """
    import pathlib
    import ubelt
    import os
    import autoflake
    import yapf
    from mypy import stubgen
    from mypy import defaults
    from xdoctest import static_analysis
    from os.path import dirname, join
    ubelt_dpath = dirname(ubelt.__file__)

    for p in pathlib.Path(ubelt_dpath).glob('*.pyi'):
        p.unlink()
    files = list(
        static_analysis.package_modpaths(ubelt_dpath,
                                         recursive=True,
                                         with_libs=1,
                                         with_pkg=0))
    files = [f for f in files if 'deprecated' not in f]
    # files = [join(ubelt_dpath, 'util_dict.py')]

    options = stubgen.Options(pyversion=defaults.PYTHON3_VERSION,
                              no_import=True,
                              doc_dir='',
                              search_path=[],
                              interpreter=sys.executable,
                              ignore_errors=False,
                              parse_only=True,
                              include_private=False,
                              output_dir=dirname(ubelt_dpath),
                              modules=[],
                              packages=[],
                              files=files,
                              verbose=False,
                              quiet=False,
                              export_less=True)
    # generate_stubs(options)

    mypy_opts = stubgen.mypy_options(options)
    py_modules, c_modules = stubgen.collect_build_targets(options, mypy_opts)

    # Collect info from docs (if given):
    sigs = class_sigs = None  # type: Optional[Dict[str, str]]
    if options.doc_dir:
        sigs, class_sigs = stubgen.collect_docs_signatures(options.doc_dir)

    # Use parsed sources to generate stubs for Python modules.
    stubgen.generate_asts_for_modules(py_modules, options.parse_only,
                                      mypy_opts, options.verbose)

    for mod in py_modules:
        assert mod.path is not None, "Not found module was not skipped"
        target = mod.module.replace('.', '/')
        if os.path.basename(mod.path) == '__init__.py':
            target += '/__init__.pyi'
        else:
            target += '.pyi'
        target = join(options.output_dir, target)
        files.append(target)
        with stubgen.generate_guarded(mod.module, target,
                                      options.ignore_errors, options.verbose):
            stubgen.generate_stub_from_ast(mod, target, options.parse_only,
                                           options.pyversion,
                                           options.include_private,
                                           options.export_less)

            gen = ExtendedStubGenerator(
                mod.runtime_all,
                pyversion=options.pyversion,
                include_private=options.include_private,
                analyzed=not options.parse_only,
                export_less=options.export_less)
            assert mod.ast is not None, "This function must be used only with analyzed modules"
            mod.ast.accept(gen)
            # print('gen.import_tracker.required_names = {!r}'.format(gen.import_tracker.required_names))
            # print(gen.import_tracker.import_lines())

            print('mod.path = {!r}'.format(mod.path))

            known_one_letter_types = {
                # 'T', 'K', 'A', 'B', 'C', 'V',
                'DT',
                'KT',
                'VT',
                'T'
            }
            for type_var_name in set(gen.import_tracker.required_names) & set(
                    known_one_letter_types):
                gen.add_typing_import('TypeVar')
                # gen.add_import_line('from typing import {}\n'.format('TypeVar'))
                gen._output = [
                    '{} = TypeVar("{}")\n'.format(type_var_name, type_var_name)
                ] + gen._output

            custom_types = {'Hasher'}
            for type_var_name in set(
                    gen.import_tracker.required_names) & set(custom_types):
                gen.add_typing_import('TypeVar')
                # gen.add_import_line('from typing import {}\n'.format('TypeVar'))
                gen._output = [
                    '{} = TypeVar("{}")\n'.format(type_var_name, type_var_name)
                ] + gen._output

            # Hack for specific module
            # if mod.path.endswith('util_path.py'):
            #     gen.add_typing_import('TypeVar')
            #     # hack for variable inheritence
            #     gen._output = ['import pathlib\nimport os\n', "_PathBase = pathlib.WindowsPath if os.name == 'nt' else pathlib.PosixPath\n"] + gen._output

            text = ''.join(gen.output())
            # Hack to remove lines caused by Py2 compat
            text = text.replace('Generator = object\n', '')
            text = text.replace('select = NotImplemented\n', '')
            text = text.replace('iteritems: Any\n', '')
            text = text.replace('text_type = str\n', '')
            text = text.replace('text_type: Any\n', '')
            text = text.replace('string_types: Any\n', '')
            text = text.replace('PY2: Any\n', '')
            text = text.replace('__win32_can_symlink__: Any\n', '')
            # text = text.replace('odict = OrderedDict', '')
            # text = text.replace('ddict = defaultdict', '')

            if mod.path.endswith('util_path.py'):
                # hack for forward reference
                text = text.replace(' -> Path:', " -> 'Path':")
                text = text.replace('class Path(_PathBase)', "class Path")

            # Format the PYI file nicely
            text = autoflake.fix_code(text,
                                      remove_unused_variables=True,
                                      remove_all_unused_imports=True)

            # import autopep8
            # text = autopep8.fix_code(text, options={
            #     'aggressive': 0,
            #     'experimental': 0,
            # })

            style = yapf.yapf_api.style.CreatePEP8Style()
            text, _ = yapf.yapf_api.FormatCode(text,
                                               filename='<stdin>',
                                               style_config=style,
                                               lines=None,
                                               verify=False)

            # print(text)

            # Write output to file.
            subdir = dirname(target)
            if subdir and not os.path.isdir(subdir):
                os.makedirs(subdir)
            with open(target, 'w') as file:
                file.write(text)