-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Expand file tree
/
Copy pathschemadefs.py
More file actions
320 lines (234 loc) · 10 KB
/
schemadefs.py
File metadata and controls
320 lines (234 loc) · 10 KB
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
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
from typing import (
Callable as _Callable,
Dict as _Dict,
Iterable as _Iterable,
Union as _Union,
)
from copy import deepcopy as _deepcopy
from misc.codegen.lib import schema as _schema
import inspect as _inspect
from dataclasses import dataclass as _dataclass
_set = set
@_dataclass
class _ChildModifier(_schema.PropertyModifier):
child: bool = True
def modify(self, prop: _schema.Property):
if prop.type is None or prop.type[0].islower():
raise _schema.Error("Non-class properties cannot be children")
if prop.is_unordered:
raise _schema.Error("Set properties cannot be children")
prop.is_child = self.child
def negate(self) -> _schema.PropertyModifier:
return _ChildModifier(False)
class _DocModifierMetaclass(type(_schema.PropertyModifier)):
# make ~doc same as doc(None)
def __invert__(self) -> _schema.PropertyModifier:
return _DocModifier(None)
@_dataclass
class _DocModifier(_schema.PropertyModifier, metaclass=_DocModifierMetaclass):
doc: str | None
def modify(self, prop: _schema.Property):
if self.doc and ("\n" in self.doc or self.doc[-1] == "."):
raise _schema.Error("No newlines or trailing dots are allowed in doc, did you intend to use desc?")
prop.doc = self.doc
def negate(self) -> _schema.PropertyModifier:
return _DocModifier(None)
class _DescModifierMetaclass(type(_schema.PropertyModifier)):
# make ~desc same as desc(None)
def __invert__(self) -> _schema.PropertyModifier:
return _DescModifier(None)
@_dataclass
class _DescModifier(_schema.PropertyModifier, metaclass=_DescModifierMetaclass):
description: str | None
def modify(self, prop: _schema.Property):
prop.description = _schema.split_doc(self.description)
def negate(self) -> _schema.PropertyModifier:
return _DescModifier(None)
def include(source: str):
# add to `includes` variable in calling context
_inspect.currentframe().f_back.f_locals.setdefault("includes", []).append(source)
imported = _schema.ImportedClass
@_dataclass
class _Namespace:
""" simple namespacing mechanism """
_name: str
def add(self, pragma: _Union["_PragmaBase", "_Parametrized"], key: str | None = None):
self.__dict__[pragma.pragma] = pragma
pragma.pragma = key or f"{self._name}_{pragma.pragma}"
@_dataclass
class _SynthModifier(_schema.PropertyModifier, _Namespace):
synth: bool = True
def modify(self, prop: _schema.Property):
prop.synth = self.synth
def negate(self) -> _schema.PropertyModifier:
return _SynthModifier(self._name, False)
qltest = _Namespace("qltest")
ql = _Namespace("ql")
cpp = _Namespace("cpp")
rust = _Namespace("rust")
synth = _SynthModifier("synth")
@_dataclass
class _PragmaBase:
pragma: str
value: object = None
def _apply(self, pragmas: _Dict[str, object]) -> None:
pragmas[self.pragma] = self.value
@_dataclass
class _ClassPragma(_PragmaBase):
""" A class pragma.
For schema classes it acts as a python decorator with `@`.
"""
inherited: bool = False
def __call__(self, cls: type) -> type:
""" use this pragma as a decorator on classes """
if self.inherited:
setattr(cls, f"{_schema.inheritable_pragma_prefix}{self.pragma}", self.value)
else:
# not using hasattr as we don't want to land on inherited pragmas
if "_pragmas" not in cls.__dict__:
cls._pragmas = {}
self._apply(cls._pragmas)
return cls
@_dataclass
class _PropertyPragma(_PragmaBase, _schema.PropertyModifier):
""" A property pragma.
It functions similarly to a `_PropertyModifier` with `|`, adding the pragma.
"""
remove: bool = False
def modify(self, prop: _schema.Property):
self._apply(prop.pragmas)
def negate(self) -> _schema.PropertyModifier:
return _PropertyPragma(self.pragma, remove=not self.remove)
def _apply(self, pragmas: _Dict[str, object]) -> None:
if self.remove:
pragmas.pop(self.pragma, None)
else:
super()._apply(pragmas)
@_dataclass
class _Pragma(_ClassPragma, _PropertyPragma):
""" A class or property pragma.
For properties, it functions similarly to a `_PropertyModifier` with `|`, adding the pragma.
For schema classes it acts as a python decorator with `@`.
"""
class _Parametrized[P, **Q, T]:
""" A parametrized pragma.
Needs to be applied to a parameter to give a pragma.
"""
def __init__(self, pragma_instance: P, factory: _Callable[Q, T]):
self.pragma_instance = pragma_instance
self.factory = factory
self.__signature__ = _inspect.signature(self.factory).replace(return_annotation=type(self.pragma_instance))
@property
def pragma(self):
return self.pragma_instance.pragma
@pragma.setter
def pragma(self, value):
self.pragma_instance.pragma = value
def __invert__(self) -> "_Parametrized[P, Q, T]":
return _Parametrized(~self.pragma_instance, factory=self.factory)
def __call__(self, *args: Q.args, **kwargs: Q.kwargs) -> T:
ret = _deepcopy(self.pragma_instance)
ret.value = self.factory(*args, **kwargs)
return ret
class _Optionalizer(_schema.PropertyModifier):
def modify(self, prop: _schema.Property):
K = _schema.Property.Kind
if prop.kind != K.SINGLE:
raise _schema.Error(
"optional should only be applied to simple property types")
prop.kind = K.OPTIONAL
class _Listifier(_schema.PropertyModifier):
def modify(self, prop: _schema.Property):
K = _schema.Property.Kind
if prop.kind == K.SINGLE:
prop.kind = K.REPEATED
elif prop.kind == K.OPTIONAL:
prop.kind = K.REPEATED_OPTIONAL
else:
raise _schema.Error(
"list should only be applied to simple or optional property types")
class _Setifier(_schema.PropertyModifier):
def modify(self, prop: _schema.Property):
K = _schema.Property.Kind
if prop.kind != K.SINGLE:
raise _schema.Error("set should only be applied to simple property types")
prop.kind = K.REPEATED_UNORDERED
class _TypeModifier:
""" Modifies types using get item notation """
def __init__(self, modifier: _schema.PropertyModifier):
self.modifier = modifier
def __getitem__(self, item):
return item | self.modifier
_ClassDecorator = _Callable[[type], type]
boolean = "boolean"
int = "int"
string = "string"
predicate = _schema.predicate_marker
optional = _TypeModifier(_Optionalizer())
list = _TypeModifier(_Listifier())
set = _TypeModifier(_Setifier())
child = _ChildModifier()
doc = _DocModifier
desc = _DescModifier
use_for_null = _ClassPragma("null")
qltest.add(_ClassPragma("skip"))
qltest.add(_ClassPragma("collapse_hierarchy"))
qltest.add(_ClassPragma("uncollapse_hierarchy"))
qltest.add(_Parametrized(_ClassPragma("test_with", inherited=True), factory=_schema.get_type_name))
ql.add(_Parametrized(_ClassPragma("default_doc_name"), factory=lambda doc: doc))
ql.add(_ClassPragma("hideable", inherited=True))
ql.add(_Pragma("internal"))
ql.add(_Parametrized(_Pragma("name"), factory=lambda name: name))
ql.add(_Parametrized(_PropertyPragma("db_table_name"), factory=lambda name: name))
cpp.add(_Pragma("skip"))
rust.add(_PropertyPragma("detach"))
rust.add(_Pragma("skip_doc_test"))
rust.add(_Parametrized(_ClassPragma("doc_test_signature"), factory=lambda signature: signature))
group = _Parametrized(_ClassPragma("group", inherited=True), factory=lambda group: group)
synth.add(_Parametrized(_ClassPragma("from_class"), factory=lambda ref: _schema.SynthInfo(
from_class=_schema.get_type_name(ref))), key="synth")
synth.add(_Parametrized(_ClassPragma("on_arguments"), factory=lambda **kwargs:
_schema.SynthInfo(on_arguments={k: _schema.get_type_name(t) for k, t in kwargs.items()})), key="synth")
@_dataclass(frozen=True)
class _PropertyModifierList(_schema.PropertyModifier):
_mods: tuple[_schema.PropertyModifier, ...]
def __or__(self, other: _schema.PropertyModifier):
return _PropertyModifierList(self._mods + (other,))
def modify(self, prop: _schema.Property):
for m in self._mods:
m.modify(prop)
_ = _PropertyModifierList(())
drop = object()
def annotate(annotated_cls: type, add_bases: _Iterable[type] | None = None, replace_bases: _Dict[type, type] | None = None, cfg: bool = False) -> _Callable[[type], _PropertyModifierList]:
"""
Add or modify schema annotations after a class has been defined previously.
The name of the class used for annotation must be `_`.
`replace_bases` can be used to replace bases on the annotated class.
"""
def decorator(cls: type) -> _PropertyModifierList:
if cls.__name__ != "_":
raise _schema.Error("Annotation classes must be named _")
if cls.__doc__ is not None:
annotated_cls.__doc__ = cls.__doc__
for p, v in cls.__dict__.get("_pragmas", {}).items():
_ClassPragma(p, value=v)(annotated_cls)
if replace_bases:
annotated_cls.__bases__ = tuple(replace_bases.get(b, b) for b in annotated_cls.__bases__)
if add_bases:
annotated_cls.__bases__ += tuple(add_bases)
annotated_cls.__cfg__ = cfg
for a in dir(cls):
if a.startswith(_schema.inheritable_pragma_prefix):
setattr(annotated_cls, a, getattr(cls, a))
for p, a in cls.__annotations__.items():
if a is drop:
del annotated_cls.__annotations__[p]
elif p in annotated_cls.__annotations__:
annotated_cls.__annotations__[p] |= a
elif isinstance(a, (_PropertyModifierList, _PropertyModifierList)):
raise _schema.Error(f"annotated property {p} not present in annotated class "
f"{annotated_cls.__name__}")
else:
annotated_cls.__annotations__[p] = a
return _
return decorator