This utility package generates Python stub files at run time to enhance the autocomplete capabilities of your favorite Python IDE.
Python is a dynamically typed language: the Python interpreter does type checking only when the code is run. This means that the type of a variable is allowed to change over its lifetime. However, Python 3.x syntax introduces type hints, which makes it possible to statically checking Python code. Despite this mechanism, Python doesn't work like other statically typed languages. In fact, type hints doesn't cause the type enforcing, but it provides only suggestions to prevent common mistakes. The most famous Python IDEs help programmers providing an autocomplete system, which suggests the type of variables, the class methods, the function parameters and so on. This makes coding faster and easier. It analyses statically the Python files and, with the help of type hints, gives to the coder a lot of useful information to improve its activity. However, Python implements the metaprogramming paradigm. The term metaprogramming refers to the potential for a program to have knowledge of or manipulate itself. In other words, Python allows generating code at run time using the metaclasses. This represents an obstacle to autocomplete systems: they cannot suggest what is defined during the code execution. To solve this issue, it is possible to use a stub file, which defines the code skeleton and the public interface of a module. By parsing this file, an autocomplete tool can provide hints and suggestions to the programmer, in order to make the coding activity faster. This utility automatically creates stub files at run time, loading the Python code and analyzing it dynamically.
- Recursion import error: the generate_stub() method fails if the file passed to constructor is the same in which the StubGenerator instance is declared. To solve it, just create another file to use StubGenerator.
The main object is called StubGenerator. It takes a Python file path and generates the corresponding stub file in the same directory.
NB: annotate as much as possible your Python file to improve the stub file quality. Use doc-strings and type hints.
To produce a stub file, run this code
StubGenerator(file_path='*.py', members_from_other_modules=['member_defined_in_other_module',...]).generate_stubs().write_to_file()
StubGenerator(file_path='*.py', members_from_other_modules=['member_defined in other_module',...]).generate_stubs().write_to_file()
. The StubGenerator object produces the stub file of the given file and save it in the same path. By default, only members defined in that file are considered, the others are ignored. It is possible to produce the stub for a member defined in another file, just use the second parameter of the constructor.
For example, if you have a Python file like this:
import sys
import numpy as np
import typing as tp
from typing import Any
class A:
pass
def f_meta(self, c: tp.Callable, i: int, a: A = A(), s: str = 'Hi') -> int:
"""
docstring
"""
return 1
class Meta(type):
def __new__(cls, classname, supers, classdict):
classdict['f_meta'] = f_meta
return type.__new__(cls, classname, supers, classdict)
class TestClass(metaclass=Meta):
def __init__(self, a: np.ndarray, b: Any):
self._a = a
def f(self, b: int) -> np.ndarray:
"""
docstring
:param b:
:return:
"""
return np.array([b])
. its name
s = 'string'
lam = lambda x: x
This module produces the the following stub file:
import sys
import numpy
import typing
class A(object, metaclass=type):
...
def f_meta(self, c: typing.Callable, i: int, a: A, s: str = 'Hi') -> int:
"""
docstring
"""
...
class Meta(type, metaclass=type):
...
class TestClass(object, metaclass=Meta):
def __init__(self, a: numpy.ndarray, b: typing.Any):
...
def f(self, b: int) -> numpy.ndarray:
"""
docstring
:param b:
:return:
"""
...
def f_meta(self, c: typing.Callable, i: int, a: A, s: str = 'Hi') -> int:
"""
docstring
"""
...
...
s: str
def lam(x):
...
To use this module successfully, you need to change your code style a little, following these tips:
-
StubGenerator considers only the types that are defined in the input file. For example, the line
MyException = Exception
is ignored, because the type MyExcpetion is another way to call Exception type, which is defined in another file. This issues can be easily overcome passing 'MyException' to StubGenerator constructor:StubGenerator(file_path='*.py', members_from_other_modules=['MyException']).generate_stubs().write_to_file()
or defining a new type:
class MyException(Exception): pass
-
the default parameters of a function are ignored if they are objects other than strings, integers, floats, etc. For example, for a function like this
def f(s = 'Hi', a = A()): pass
the output will bedef f(s = 'Hi', a): pass
. This is an error for the Python syntax, just invert the parameter.