= Path('test_py2pyi.py') fn
Create delegated pyi
Setup
Basics
imp_mod
imp_mod (module_path, package=None)
Import dynamically the module referenced in fn
= imp_mod(fn)
mod = mod.A()
a a.h()
1
= _get_tree(mod) tree
AST.__repr__
AST.__repr__ ()
# for o in enumerate(tree.body): print(o)
= tree.body[4]
node node
def f(a: int, b: str='a') -> str:
"""I am f"""
return 1
isinstance(node, functypes)
True
has_deco
has_deco (node:Union[ast.FunctionDef,ast.AsyncFunctionDef], name:str)
Check if a function node node
has a decorator named name
= 'delegates'
nm has_deco(node, nm)
False
= tree.body[5]
node node
@delegates(f)
def g(c, d: X, **kwargs) -> str:
"""I am g"""
return 2
has_deco(node, nm)
True
Function processing
def _proc_body (node, mod): print('_proc_body', type(node))
def _proc_func (node, mod): print('_proc_func', type(node))
def _proc_class (node, mod): print('_proc_class', type(node))
def _proc_patched(node, mod): print('_proc_patched', type(node))
= copy.deepcopy(tree.body[7])
parent_node = copy.deepcopy(tree.body[10])
patched_node "patch"), True)
test_is(has_deco(patched_node, str(patched_node.args.args[0].annotation), parent_node.name)
test_eq(
_clean_patched_node(patched_node)"patch"), False)
test_is(has_deco(patched_node, 0].annotation, None) test_eq(patched_node.args.args[
= ast.parse('''
empty_cls1, empty_cls2, empty_cls3 class A:
"""An empty class."""
class B:
pass
class C:
...
''').body
True)
test_is(_is_empty_class(empty_cls1), True)
test_is(_is_empty_class(empty_cls2), True)
test_is(_is_empty_class(empty_cls3),
= ast.parse('''
non_empty_cls, empty_func class A:
a = 1
def f():
...
''').body
False)
test_is(_is_empty_class(non_empty_cls), False) test_is(_is_empty_class(empty_func),
# we could have reused `parent_node` and `patched_node` from the previous cells.
# copying them here allows us to run this cell multiple times which makes it a little easier to write tests.
= copy.deepcopy(tree.body[7])
parent_node = copy.deepcopy(tree.body[11])
patched_node len(parent_node.body),1)
test_eq(
_add_patched_node_to_parent(patched_node, parent_node)len(parent_node.body),2)
test_eq(-1], patched_node)
test_eq(parent_node.body[
# patched node replaces an existing class method (A.h)
= ast.parse("""
patched_h_node @patch
def h(self: A, *args, **kwargs):
...
""", mode='single').body[0]
_add_patched_node_to_parent(patched_h_node, parent_node)len(parent_node.body), 2)
test_eq(0], patched_h_node)
test_eq(parent_node.body[
# patched node is added to an empty class
= ast.parse('''
empty_cls, patched_node class Z:
"""An empty class."""
@patch
def a(self: Z, *args, **kwargs):
...
''').body
len(empty_cls.body), 1)
test_eq(0], patched_node)
test_ne(empty_cls.body[
_add_patched_node_to_parent(patched_node, empty_cls)len(empty_cls.body), 1)
test_eq(0], patched_node) test_eq(empty_cls.body[
= _get_tree(mod)
raw_tree = _proc_mod(mod)
processed_tree = len(raw_tree.body)
n_raw_tree_nodes # mod contains 3 patch methods so our processed_tree should have 3 less nodes
len(processed_tree.body), n_raw_tree_nodes-3) test_eq(
_proc_class <class 'ast.ClassDef'>
_proc_body <class 'ast.FunctionDef'>
_proc_func <class 'ast.FunctionDef'>
_proc_body <class 'ast.FunctionDef'>
_proc_class <class 'ast.ClassDef'>
_proc_class <class 'ast.ClassDef'>
_proc_patched <class 'ast.FunctionDef'>
_proc_patched <class 'ast.FunctionDef'>
_proc_body <class 'ast.FunctionDef'>
; _proc_mod(mod)
_proc_class <class 'ast.ClassDef'>
_proc_body <class 'ast.FunctionDef'>
_proc_func <class 'ast.FunctionDef'>
_proc_body <class 'ast.FunctionDef'>
_proc_class <class 'ast.ClassDef'>
_proc_class <class 'ast.ClassDef'>
_proc_patched <class 'ast.FunctionDef'>
_proc_patched <class 'ast.FunctionDef'>
_proc_body <class 'ast.FunctionDef'>
node.name
'g'
= getattr(mod, node.name)
sym sym
<function test_py2pyi.g(c, d: test_py2pyi.X, *, b: str = 'a') -> str>
= signature(sym)
sig print(sig)
(c, d: test_py2pyi.X, *, b: str = 'a') -> str
sig2str
sig2str (sig)
ast_args
ast_args (func)
= ast_args(sym)
newargs newargs
*, b: str='a' c, d: test_py2pyi.X,
node.args
**kwargs c, d: X,
= newargs
node.args node
@delegates(f)
def g(c, d: test_py2pyi.X, *, b: str='a') -> str:
"""I am g"""
return 2
_body_ellip(node) node
@delegates(f)
def g(c, d: test_py2pyi.X, *, b: str='a') -> str:
"""I am g"""
...
= _get_tree(mod)
tree = tree.body[5]
node node
@delegates(f)
def g(c, d: X, **kwargs) -> str:
"""I am g"""
return 2
_update_func(node, sym) node
def g(c, d: X, *, b: str='a') -> str:
"""I am g"""
...
= _proc_mod(mod)
tree 5] tree.body[
_proc_class <class 'ast.ClassDef'>
_proc_class <class 'ast.ClassDef'>
_proc_class <class 'ast.ClassDef'>
_proc_patched <class 'ast.FunctionDef'>
_proc_patched <class 'ast.FunctionDef'>
def g(c, d: X, *, b: str='a') -> str:
"""I am g"""
...
Patch
= _get_tree(mod)
tree = tree.body[9]
node node
@patch
@delegates(j)
def k(self: (A, B), b: bool=False, **kwargs):
return 1
= node.args.args[0].annotation ann
if hasattr(ann, 'elts'): ann = ann.elts[0]
= ann.id
nm nm
'A'
= getattr(mod, nm)
cls = getattr(cls, node.name) sym
sig2str(signature(sym))
"(self: (test_py2pyi.A, test_py2pyi.B), b: bool = False, *, d: str = 'a')"
_update_func(node, sym)
node
@patch
def k(self: (A, B), b: bool=False, *, d: str='a'):
...
= _get_tree(mod)
tree 9] tree.body[
@patch
@delegates(j)
def k(self: (A, B), b: bool=False, **kwargs):
return 1
Class and file
= _get_tree(mod)
tree = tree.body[7]
node node
class A:
@delegates(j)
def h(self, b: bool=False, **kwargs):
= 1
a return a
node.body
[@delegates(j)
def h(self, b: bool=False, **kwargs):
a = 1
return a]
= _proc_mod(mod)
tree 7] tree.body[
class A:
def h(self, b: bool=False, *, d: str='a'):
...
def k(self, b: bool=False, *, d: str='a'):
...
def m(self, b: bool=False, *, d: str='a'):
...
def n(self, b: bool=False, **kwargs):
"""No delegates here mmm'k?"""
...
create_pyi
create_pyi (fn, package=None)
Convert fname.py
to fname.pyi
by removing function bodies and expanding delegates
kwargs
create_pyi(fn)
# fn = Path('/Users/jhoward/git/fastcore/fastcore/docments.py')
# create_pyi(fn, 'fastcore')
Script
py2pyi
py2pyi (fname:str, package:str=None)
Convert fname.py
to fname.pyi
by removing function bodies and expanding delegates
kwargs
Type | Default | Details | |
---|---|---|---|
fname | str | The file name to convert | |
package | str | None | The parent package |
replace_wildcards
replace_wildcards (path:str)
Expand wildcard imports in the specified Python file.
Type | Details | |
---|---|---|
path | str | Path to the Python file to process |