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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
|
from __future__ import annotations
import dataclasses as dc
import copy
import enum
import functools
import inspect
from typing import Final, Any, TYPE_CHECKING
if TYPE_CHECKING:
from clinic import Clinic
from libclinic.converter import CConverter
from libclinic.converters import self_converter
from libclinic.return_converters import CReturnConverter
from libclinic import VersionTuple, unspecified
ClassDict = dict[str, "Class"]
ModuleDict = dict[str, "Module"]
ParamDict = dict[str, "Parameter"]
@dc.dataclass(repr=False)
class Module:
name: str
module: Module | Clinic
def __post_init__(self) -> None:
self.parent = self.module
self.modules: ModuleDict = {}
self.classes: ClassDict = {}
self.functions: list[Function] = []
def __repr__(self) -> str:
return "<clinic.Module " + repr(self.name) + " at " + str(id(self)) + ">"
@dc.dataclass(repr=False)
class Class:
name: str
module: Module | Clinic
cls: Class | None
typedef: str
type_object: str
def __post_init__(self) -> None:
self.parent = self.cls or self.module
self.classes: ClassDict = {}
self.functions: list[Function] = []
def __repr__(self) -> str:
return "<clinic.Class " + repr(self.name) + " at " + str(id(self)) + ">"
class FunctionKind(enum.Enum):
INVALID = enum.auto()
CALLABLE = enum.auto()
STATIC_METHOD = enum.auto()
CLASS_METHOD = enum.auto()
METHOD_INIT = enum.auto()
METHOD_NEW = enum.auto()
GETTER = enum.auto()
SETTER = enum.auto()
@functools.cached_property
def new_or_init(self) -> bool:
return self in {FunctionKind.METHOD_INIT, FunctionKind.METHOD_NEW}
def __repr__(self) -> str:
return f"<clinic.FunctionKind.{self.name}>"
INVALID: Final = FunctionKind.INVALID
CALLABLE: Final = FunctionKind.CALLABLE
STATIC_METHOD: Final = FunctionKind.STATIC_METHOD
CLASS_METHOD: Final = FunctionKind.CLASS_METHOD
METHOD_INIT: Final = FunctionKind.METHOD_INIT
METHOD_NEW: Final = FunctionKind.METHOD_NEW
GETTER: Final = FunctionKind.GETTER
SETTER: Final = FunctionKind.SETTER
@dc.dataclass(repr=False)
class Function:
"""
Mutable duck type for inspect.Function.
docstring - a str containing
* embedded line breaks
* text outdented to the left margin
* no trailing whitespace.
It will always be true that
(not docstring) or ((not docstring[0].isspace()) and (docstring.rstrip() == docstring))
"""
parameters: ParamDict = dc.field(default_factory=dict)
_: dc.KW_ONLY
name: str
module: Module | Clinic
cls: Class | None
c_basename: str
full_name: str
return_converter: CReturnConverter
kind: FunctionKind
coexist: bool
return_annotation: object = inspect.Signature.empty
docstring: str = ''
# docstring_only means "don't generate a machine-readable
# signature, just a normal docstring". it's True for
# functions with optional groups because we can't represent
# those accurately with inspect.Signature in 3.4.
docstring_only: bool = False
critical_section: bool = False
target_critical_section: list[str] = dc.field(default_factory=list)
def __post_init__(self) -> None:
self.parent = self.cls or self.module
self.self_converter: self_converter | None = None
self.__render_parameters__: list[Parameter] | None = None
@functools.cached_property
def displayname(self) -> str:
"""Pretty-printable name."""
if self.kind.new_or_init:
assert isinstance(self.cls, Class)
return self.cls.name
else:
return self.name
@functools.cached_property
def fulldisplayname(self) -> str:
parent: Class | Module | Clinic | None
if self.kind.new_or_init:
parent = getattr(self.cls, "parent", None)
else:
parent = self.parent
name = self.displayname
while isinstance(parent, (Module, Class)):
name = f"{parent.name}.{name}"
parent = parent.parent
return name
@property
def render_parameters(self) -> list[Parameter]:
if not self.__render_parameters__:
l: list[Parameter] = []
self.__render_parameters__ = l
for p in self.parameters.values():
p = p.copy()
p.converter.pre_render()
l.append(p)
return self.__render_parameters__
@property
def methoddef_flags(self) -> str | None:
if self.kind.new_or_init:
return None
flags = []
match self.kind:
case FunctionKind.CLASS_METHOD:
flags.append('METH_CLASS')
case FunctionKind.STATIC_METHOD:
flags.append('METH_STATIC')
case _ as kind:
acceptable_kinds = {FunctionKind.CALLABLE, FunctionKind.GETTER, FunctionKind.SETTER}
assert kind in acceptable_kinds, f"unknown kind: {kind!r}"
if self.coexist:
flags.append('METH_COEXIST')
return '|'.join(flags)
def __repr__(self) -> str:
return f'<clinic.Function {self.name!r}>'
def copy(self, **overrides: Any) -> Function:
f = dc.replace(self, **overrides)
f.parameters = {
name: value.copy(function=f)
for name, value in f.parameters.items()
}
return f
@dc.dataclass(repr=False, slots=True)
class Parameter:
"""
Mutable duck type of inspect.Parameter.
"""
name: str
kind: inspect._ParameterKind
_: dc.KW_ONLY
default: object = inspect.Parameter.empty
function: Function
converter: CConverter
annotation: object = inspect.Parameter.empty
docstring: str = ''
group: int = 0
# (`None` signifies that there is no deprecation)
deprecated_positional: VersionTuple | None = None
deprecated_keyword: VersionTuple | None = None
right_bracket_count: int = dc.field(init=False, default=0)
def __repr__(self) -> str:
return f'<clinic.Parameter {self.name!r}>'
def is_keyword_only(self) -> bool:
return self.kind == inspect.Parameter.KEYWORD_ONLY
def is_positional_only(self) -> bool:
return self.kind == inspect.Parameter.POSITIONAL_ONLY
def is_vararg(self) -> bool:
return self.kind == inspect.Parameter.VAR_POSITIONAL
def is_optional(self) -> bool:
return not self.is_vararg() and (self.default is not unspecified)
def copy(
self,
/,
*,
converter: CConverter | None = None,
function: Function | None = None,
**overrides: Any
) -> Parameter:
function = function or self.function
if not converter:
converter = copy.copy(self.converter)
converter.function = function
return dc.replace(self, **overrides, function=function, converter=converter)
def get_displayname(self, i: int) -> str:
if i == 0:
return 'argument'
if not self.is_positional_only():
return f'argument {self.name!r}'
else:
return f'argument {i}'
def render_docstring(self) -> str:
lines = [f" {self.name}"]
lines.extend(f" {line}" for line in self.docstring.split("\n"))
return "\n".join(lines).rstrip()
|