1- from typing import Callable as _Callable
1+ from typing import Callable as _Callable , List as _List
22from misc .codegen .lib import schema as _schema
33import inspect as _inspect
44from dataclasses import dataclass as _dataclass
55
6+ from misc .codegen .lib .schema import Property
67
8+
9+ @_dataclass
710class _ChildModifier (_schema .PropertyModifier ):
11+ child : bool = True
12+
813 def modify (self , prop : _schema .Property ):
914 if prop .type is None or prop .type [0 ].islower ():
1015 raise _schema .Error ("Non-class properties cannot be children" )
1116 if prop .is_unordered :
1217 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 )
1428
1529
1630@_dataclass
17- class _DocModifier (_schema .PropertyModifier ):
18- doc : str
31+ class _DocModifier (_schema .PropertyModifier , metaclass = _DocModifierMetaclass ):
32+ doc : str | None
1933
2034 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 ] == "." ) :
2236 raise _schema .Error ("No newlines or trailing dots are allowed in doc, did you intend to use desc?" )
2337 prop .doc = self .doc
2438
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+
2548
2649@_dataclass
27- class _DescModifier (_schema .PropertyModifier ):
28- description : str
50+ class _DescModifier (_schema .PropertyModifier , metaclass = _DescModifierMetaclass ):
51+ description : str | None
2952
3053 def modify (self , prop : _schema .Property ):
3154 prop .description = _schema .split_doc (self .description )
3255
56+ def negate (self ) -> _schema .PropertyModifier :
57+ return _DescModifier (None )
58+
3359
3460def include (source : str ):
3561 # 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 )
3863
3964
4065class _Namespace :
@@ -44,9 +69,15 @@ def __init__(self, **kwargs):
4469 self .__dict__ .update (kwargs )
4570
4671
72+ @_dataclass
4773class _SynthModifier (_schema .PropertyModifier , _Namespace ):
74+ synth : bool = True
75+
4876 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 )
5081
5182
5283qltest = _Namespace ()
@@ -63,22 +94,35 @@ class _Pragma(_schema.PropertyModifier):
6394 For schema classes it acts as a python decorator with `@`.
6495 """
6596 pragma : str
97+ remove : bool = False
6698
6799 def __post_init__ (self ):
68100 namespace , _ , name = self .pragma .partition ('_' )
69101 setattr (globals ()[namespace ], name , self )
70102
71103 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 )
73108
74109 def __call__ (self , cls : type ) -> type :
75110 """ use this pragma as a decorator on classes """
76111 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 :
79114 cls ._pragmas = [self .pragma ]
80115 return cls
81116
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+
82126
83127class _Optionalizer (_schema .PropertyModifier ):
84128 def modify (self , prop : _schema .Property ):
@@ -172,17 +216,57 @@ def group(name: str = "") -> _ClassDecorator:
172216 synth = _schema .SynthInfo (on_arguments = {k : _schema .get_type_name (t ) for k , t in kwargs .items ()}))
173217
174218
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 ]:
176241 """
177242 Add or modify schema annotations after a class has been defined
178243 For the moment, only docstring annotation is supported. In the future, any kind of
179244 modification will be allowed.
180245
181246 The name of the class used for annotation must be `_`
182247 """
183- def decorator (cls : type ) -> None :
248+ def decorator (cls : type ) -> _PropertyAnnotation :
184249 if cls .__name__ != "_" :
185250 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 _
188272 return decorator
0 commit comments