-
Notifications
You must be signed in to change notification settings - Fork 6
/
gen_pyi.py
192 lines (148 loc) · 7.38 KB
/
gen_pyi.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
import glob
import inspect
import os
import re
import typing
old_files = glob.glob("pyhornedowl/**/__init__.*", recursive=True)
for f in old_files:
os.unlink(f)
import pyhornedowl.pyhornedowl as pho
os.makedirs("pyhornedowl/model", exist_ok=True)
with open("pyhornedowl/__init__.py", "w") as f:
pyhornedowl_members = [k for k, v in pho.__dict__.items() if isinstance(v, type) or callable(v)]
f.write("from __future__ import annotations\n")
f.write("from .pyhornedowl import ")
f.write(", ".join(pyhornedowl_members))
f.write("\n\n")
f.write("__all__ = [")
f.write(", ".join([f'"{n}"' for n in pyhornedowl_members]))
f.write("]\n")
implemented_magic = [f"__{x}__" for x in
["invert", "and", "or", "invert", "iter", "getitem", "setitem", "contains", "len", "delitem"]]
with open("pyhornedowl/__init__.pyi", "w") as f:
f.write("import typing\n")
f.write("from typing import *\n")
f.write("from typing_extensions import deprecated\n\n")
f.write("import model\n")
f.write("\n")
for name, entry in pho.__dict__.items():
if isinstance(entry, type):
f.write(f"class {name}:\n")
# There appears to be a bug with pyo3. Documentation on enum
# variants is not attached to their mapped python types. Hence we
# use a workarround of adding their documentation to the enum in
# the style: "<MemberName>: <doc string>".
member_docs = {}
if hasattr(entry, "__doc__"):
entry_doc = entry.__doc__
if entry_doc is not None:
f.write(" \"\"\"\n")
for line in entry_doc.splitlines():
member_doc_m = re.match(r"^(\w+): (.*)$", line)
if member_doc_m:
member_docs[member_doc_m.group(1)]=member_doc_m.group(2)
else:
f.write(f" {line}\n")
f.write(" \"\"\"\n")
for member_name, member in entry.__dict__.items():
if member_name.startswith("_") and member_name not in implemented_magic:
continue
# E.g. for enums
if isinstance(member, entry):
f.write(f" {member_name}: typing.Self\n")
if member_name in member_docs or hasattr(member, "__doc__") and member.__doc__ is not None:
doc = member_docs.get(member_name, getattr(member, "__doc__"))
f.write(" \"\"\"\n")
for line in doc.splitlines():
f.write(f" {line}\n")
f.write(" \"\"\"\n")
continue
if hasattr(member, "__doc__"):
doc = member.__doc__
if doc is not None:
lines = doc.splitlines()
if len(lines) > 2:
annotations_end = lines.index(next(x for x in lines if not x.startswith("@")), 0)
annotations = lines[:annotations_end]
sign = lines[annotations_end]
for ann in annotations:
f.write(f" {ann}\n")
if callable(member):
f.write(f" def {sign}:\n")
doc = "\n".join([f" {l}" for l in lines[annotations_end+2:]])
f.write(f' """\n{doc}\n """\n ...\n\n')
else:
f.write(f" {sign}\n")
doc = "\n".join([f" {l}" for l in lines[annotations_end+2:]])
f.write(f' """\n{doc}\n """\n\n')
continue
if callable(member):
f.write(f" def {member_name}{inspect.signature(member)}:\n ...\n\n")
continue
f.write("\n")
elif callable(entry):
if hasattr(entry, "__doc__"):
doc = entry.__doc__
if doc is not None:
lines = doc.splitlines()
if len(lines) > 2:
annotations_end = lines.index(next(x for x in lines if not x.startswith("@")), 0)
annotations = lines[:annotations_end]
sign = lines[annotations_end]
for ann in annotations:
f.write(f"{ann}\n")
f.write(f"def {sign}:\n")
doc = "\n".join([f" {l}" for l in lines[annotations_end+2:]])
f.write(f' """\n{doc}\n """\n ...\n\n')
f.write("\n")
with open("pyhornedowl/py.typed", "w"):
pass
def handle_module(module: str):
with open(f"pyhornedowl/{module}/__init__.py", "w") as f:
f.write(f"from ..pyhornedowl import {module}\n")
f.write("import typing\n\n")
al = []
for name, entry in getattr(pho, module).__dict__.items():
if not isinstance(entry, type) and not type(entry) == typing._UnionGenericAlias:
continue
f.write(f"{name} = {module}.{name}\n")
al.append(name)
f.write("\n")
f.write(f"__all__ = {al}")
with open(f"pyhornedowl/{module}/__init__.pyi", "w") as f:
f.write("import typing\n")
f.write("from typing import *\n")
f.write("from typing_extensions import deprecated\n\n")
for name, entry in getattr(pho, module).__dict__.items():
if isinstance(entry, type):
if "__pyi__" in entry.__dict__:
pyi: str = entry.__pyi__()
pyi = re.sub(r" from: .*$|from: .*?[,)]", "", pyi)
f.write(pyi)
# Also look for methods
for member_name, member in entry.__dict__.items():
if member_name.startswith("_") and member_name not in implemented_magic:
continue
if hasattr(member, "__doc__"):
doc = member.__doc__
if doc is not None:
lines = doc.splitlines()
if len(lines) > 2:
sign = lines[0]
f.write(f" def {sign}:\n")
doc = "\n".join([f" {l}" for l in lines[2:]])
f.write(f' """\n{doc}\n """\n ...\n\n')
else:
f.write(f"class {name}:\n")
for attr_name, attr in entry.__dict__.items():
if attr_name.startswith("_") or attr_name == "from":
continue
f.write(f" {attr_name}: Any\n")
f.write(" ...\n")
elif type(entry) == typing._UnionGenericAlias:
f.write(f"{name} = {str(entry).replace(f'pyhornedowl.{module}.', '')}")
else:
continue
f.write("\n")
f.write("\n")
handle_module("model")