1
- from typing import Callable as _Callable
1
+ from typing import Callable as _Callable , List as _List
2
2
from misc .codegen .lib import schema as _schema
3
3
import inspect as _inspect
4
4
from dataclasses import dataclass as _dataclass
5
5
6
+ from misc .codegen .lib .schema import Property
6
7
8
+
9
+ @_dataclass
7
10
class _ChildModifier (_schema .PropertyModifier ):
11
+ child : bool = True
12
+
8
13
def modify (self , prop : _schema .Property ):
9
14
if prop .type is None or prop .type [0 ].islower ():
10
15
raise _schema .Error ("Non-class properties cannot be children" )
11
16
if prop .is_unordered :
12
17
raise _schema .Error ("Set properties cannot be children" )
13
- prop .is_child = True
18
+ prop .is_child = self .child
19
+
20
+ def negate (self ) -> _schema .PropertyModifier :
21
+ return _ChildModifier (False )
22
+
23
+
24
+ class _DocModifierMetaclass (type (_schema .PropertyModifier )):
25
+ # make ~doc same as doc(None)
26
+ def __invert__ (self ) -> _schema .PropertyModifier :
27
+ return _DocModifier (None )
14
28
15
29
16
30
@_dataclass
17
- class _DocModifier (_schema .PropertyModifier ):
18
- doc : str
31
+ class _DocModifier (_schema .PropertyModifier , metaclass = _DocModifierMetaclass ):
32
+ doc : str | None
19
33
20
34
def modify (self , prop : _schema .Property ):
21
- if "\n " in self .doc or self .doc [- 1 ] == "." :
35
+ if self . doc and ( "\n " in self .doc or self .doc [- 1 ] == "." ) :
22
36
raise _schema .Error ("No newlines or trailing dots are allowed in doc, did you intend to use desc?" )
23
37
prop .doc = self .doc
24
38
39
+ def negate (self ) -> _schema .PropertyModifier :
40
+ return _DocModifier (None )
41
+
42
+
43
+ class _DescModifierMetaclass (type (_schema .PropertyModifier )):
44
+ # make ~desc same as desc(None)
45
+ def __invert__ (self ) -> _schema .PropertyModifier :
46
+ return _DescModifier (None )
47
+
25
48
26
49
@_dataclass
27
- class _DescModifier (_schema .PropertyModifier ):
28
- description : str
50
+ class _DescModifier (_schema .PropertyModifier , metaclass = _DescModifierMetaclass ):
51
+ description : str | None
29
52
30
53
def modify (self , prop : _schema .Property ):
31
54
prop .description = _schema .split_doc (self .description )
32
55
56
+ def negate (self ) -> _schema .PropertyModifier :
57
+ return _DescModifier (None )
58
+
33
59
34
60
def include (source : str ):
35
61
# add to `includes` variable in calling context
36
- _inspect .currentframe ().f_back .f_locals .setdefault (
37
- "__includes" , []).append (source )
62
+ _inspect .currentframe ().f_back .f_locals .setdefault ("includes" , []).append (source )
38
63
39
64
40
65
class _Namespace :
@@ -44,9 +69,15 @@ def __init__(self, **kwargs):
44
69
self .__dict__ .update (kwargs )
45
70
46
71
72
+ @_dataclass
47
73
class _SynthModifier (_schema .PropertyModifier , _Namespace ):
74
+ synth : bool = True
75
+
48
76
def modify (self , prop : _schema .Property ):
49
- prop .synth = True
77
+ prop .synth = self .synth
78
+
79
+ def negate (self ) -> "PropertyModifier" :
80
+ return _SynthModifier (False )
50
81
51
82
52
83
qltest = _Namespace ()
@@ -63,22 +94,35 @@ class _Pragma(_schema.PropertyModifier):
63
94
For schema classes it acts as a python decorator with `@`.
64
95
"""
65
96
pragma : str
97
+ remove : bool = False
66
98
67
99
def __post_init__ (self ):
68
100
namespace , _ , name = self .pragma .partition ('_' )
69
101
setattr (globals ()[namespace ], name , self )
70
102
71
103
def modify (self , prop : _schema .Property ):
72
- prop .pragmas .append (self .pragma )
104
+ self ._apply (prop .pragmas )
105
+
106
+ def negate (self ) -> "PropertyModifier" :
107
+ return _Pragma (self .pragma , remove = True )
73
108
74
109
def __call__ (self , cls : type ) -> type :
75
110
""" use this pragma as a decorator on classes """
76
111
if "_pragmas" in cls .__dict__ : # not using hasattr as we don't want to land on inherited pragmas
77
- cls . _pragmas . append ( self . pragma )
78
- else :
112
+ self . _apply ( cls . _pragmas )
113
+ elif not self . remove :
79
114
cls ._pragmas = [self .pragma ]
80
115
return cls
81
116
117
+ def _apply (self , pragmas : _List [str ]) -> None :
118
+ if self .remove :
119
+ try :
120
+ pragmas .remove (self .pragma )
121
+ except ValueError :
122
+ pass
123
+ else :
124
+ pragmas .append (self .pragma )
125
+
82
126
83
127
class _Optionalizer (_schema .PropertyModifier ):
84
128
def modify (self , prop : _schema .Property ):
@@ -172,17 +216,57 @@ def group(name: str = "") -> _ClassDecorator:
172
216
synth = _schema .SynthInfo (on_arguments = {k : _schema .get_type_name (t ) for k , t in kwargs .items ()}))
173
217
174
218
175
- def annotate (annotated_cls : type ) -> _Callable [[type ], None ]:
219
+ class _PropertyModifierList (_schema .PropertyModifier ):
220
+ def __init__ (self ):
221
+ self ._mods = []
222
+
223
+ def __or__ (self , other : _schema .PropertyModifier ):
224
+ self ._mods .append (other )
225
+ return self
226
+
227
+ def modify (self , prop : Property ):
228
+ for m in self ._mods :
229
+ m .modify (prop )
230
+
231
+
232
+ class _PropertyAnnotation :
233
+ def __or__ (self , other : _schema .PropertyModifier ):
234
+ return _PropertyModifierList () | other
235
+
236
+
237
+ _ = _PropertyAnnotation ()
238
+
239
+
240
+ def annotate (annotated_cls : type ) -> _Callable [[type ], _PropertyAnnotation ]:
176
241
"""
177
242
Add or modify schema annotations after a class has been defined
178
243
For the moment, only docstring annotation is supported. In the future, any kind of
179
244
modification will be allowed.
180
245
181
246
The name of the class used for annotation must be `_`
182
247
"""
183
- def decorator (cls : type ) -> None :
248
+ def decorator (cls : type ) -> _PropertyAnnotation :
184
249
if cls .__name__ != "_" :
185
250
raise _schema .Error ("Annotation classes must be named _" )
186
- annotated_cls .__doc__ = cls .__doc__
187
- return None
251
+ if cls .__doc__ is not None :
252
+ annotated_cls .__doc__ = cls .__doc__
253
+ old_pragmas = getattr (annotated_cls , "_pragmas" , None )
254
+ new_pragmas = getattr (cls , "_pragmas" , [])
255
+ if old_pragmas :
256
+ old_pragmas .extend (new_pragmas )
257
+ else :
258
+ annotated_cls ._pragmas = new_pragmas
259
+ for a , v in cls .__dict__ .items ():
260
+ # transfer annotations
261
+ if a .startswith ("_" ) and not a .startswith ("__" ) and a != "_pragmas" :
262
+ setattr (annotated_cls , a , v )
263
+ for p , a in cls .__annotations__ .items ():
264
+ if p in annotated_cls .__annotations__ :
265
+ annotated_cls .__annotations__ [p ] |= a
266
+ elif isinstance (a , (_PropertyAnnotation , _PropertyModifierList )):
267
+ raise _schema .Error (f"annotated property { p } not present in annotated class "
268
+ f"{ annotated_cls .__name__ } " )
269
+ else :
270
+ annotated_cls .__annotations__ [p ] = a
271
+ return _
188
272
return decorator
0 commit comments