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+ # make ~doc same as doc(None)
24+
25+
26+ class _DocModifierMetaclass (type (_schema .PropertyModifier )):
27+ def __invert__ (self ) -> _schema .PropertyModifier :
28+ return _DocModifier (None )
1429
1530
1631@_dataclass
17- class _DocModifier (_schema .PropertyModifier ):
18- doc : str
32+ class _DocModifier (_schema .PropertyModifier , metaclass = _DocModifierMetaclass ):
33+ doc : str | None
1934
2035 def modify (self , prop : _schema .Property ):
21- if "\n " in self .doc or self .doc [- 1 ] == "." :
36+ if self . doc and ( "\n " in self .doc or self .doc [- 1 ] == "." ) :
2237 raise _schema .Error ("No newlines or trailing dots are allowed in doc, did you intend to use desc?" )
2338 prop .doc = self .doc
2439
40+ def negate (self ) -> _schema .PropertyModifier :
41+ return _DocModifier (None )
42+
43+
44+ # make ~desc same as desc(None)
45+ class _DescModifierMetaclass (type (_schema .PropertyModifier )):
46+ def __invert__ (self ) -> _schema .PropertyModifier :
47+ return _DescModifier (None )
48+
2549
2650@_dataclass
27- class _DescModifier (_schema .PropertyModifier ):
28- description : str
51+ class _DescModifier (_schema .PropertyModifier , metaclass = _DescModifierMetaclass ):
52+ description : str | None
2953
3054 def modify (self , prop : _schema .Property ):
3155 prop .description = _schema .split_doc (self .description )
3256
57+ def negate (self ) -> _schema .PropertyModifier :
58+ return _DescModifier (None )
59+
3360
3461def include (source : str ):
3562 # add to `includes` variable in calling context
36- _inspect .currentframe ().f_back .f_locals .setdefault (
37- "__includes" , []).append (source )
63+ _inspect .currentframe ().f_back .f_locals .setdefault ("includes" , []).append (source )
3864
3965
4066class _Namespace :
@@ -44,9 +70,15 @@ def __init__(self, **kwargs):
4470 self .__dict__ .update (kwargs )
4571
4672
73+ @_dataclass
4774class _SynthModifier (_schema .PropertyModifier , _Namespace ):
75+ synth : bool = True
76+
4877 def modify (self , prop : _schema .Property ):
49- prop .synth = True
78+ prop .synth = self .synth
79+
80+ def negate (self ) -> "PropertyModifier" :
81+ return _SynthModifier (False )
5082
5183
5284qltest = _Namespace ()
@@ -63,22 +95,35 @@ class _Pragma(_schema.PropertyModifier):
6395 For schema classes it acts as a python decorator with `@`.
6496 """
6597 pragma : str
98+ remove : bool = False
6699
67100 def __post_init__ (self ):
68101 namespace , _ , name = self .pragma .partition ('_' )
69102 setattr (globals ()[namespace ], name , self )
70103
71104 def modify (self , prop : _schema .Property ):
72- prop .pragmas .append (self .pragma )
105+ self ._apply (prop .pragmas )
106+
107+ def negate (self ) -> "PropertyModifier" :
108+ return _Pragma (self .pragma , remove = True )
73109
74110 def __call__ (self , cls : type ) -> type :
75111 """ use this pragma as a decorator on classes """
76112 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 :
113+ self . _apply ( cls . _pragmas )
114+ elif not self . remove :
79115 cls ._pragmas = [self .pragma ]
80116 return cls
81117
118+ def _apply (self , pragmas : _List [str ]) -> None :
119+ if self .remove :
120+ try :
121+ pragmas .remove (self .pragma )
122+ except ValueError :
123+ pass
124+ else :
125+ pragmas .append (self .pragma )
126+
82127
83128class _Optionalizer (_schema .PropertyModifier ):
84129 def modify (self , prop : _schema .Property ):
@@ -172,17 +217,46 @@ def group(name: str = "") -> _ClassDecorator:
172217 synth = _schema .SynthInfo (on_arguments = {k : _schema .get_type_name (t ) for k , t in kwargs .items ()}))
173218
174219
175- def annotate (annotated_cls : type ) -> _Callable [[type ], None ]:
220+ class _PropertyModifierList (_schema .PropertyModifier ):
221+ def __init__ (self ):
222+ self ._mods = []
223+
224+ def __or__ (self , other : _schema .PropertyModifier ):
225+ self ._mods .append (other )
226+ return self
227+
228+ def modify (self , prop : Property ):
229+ for m in self ._mods :
230+ m .modify (prop )
231+
232+
233+ class _PropertyAnnotation :
234+ def __or__ (self , other : _schema .PropertyModifier ):
235+ return _PropertyModifierList () | other
236+
237+
238+ _ = _PropertyAnnotation ()
239+
240+
241+ def annotate (annotated_cls : type ) -> _Callable [[type ], _PropertyAnnotation ]:
176242 """
177243 Add or modify schema annotations after a class has been defined
178244 For the moment, only docstring annotation is supported. In the future, any kind of
179245 modification will be allowed.
180246
181247 The name of the class used for annotation must be `_`
182248 """
183- def decorator (cls : type ) -> None :
249+ def decorator (cls : type ) -> _PropertyAnnotation :
184250 if cls .__name__ != "_" :
185251 raise _schema .Error ("Annotation classes must be named _" )
186252 annotated_cls .__doc__ = cls .__doc__
187- return None
253+ for p , a in cls .__annotations__ .items ():
254+ if p in annotated_cls .__annotations__ :
255+ annotated_cls .__annotations__ [p ] |= a
256+ elif isinstance (a , (_PropertyAnnotation , _PropertyModifierList )):
257+ raise _schema .Error (f"annotated property { p } not present in annotated class "
258+ f"{ annotated_cls .__name__ } " )
259+ else :
260+ annotated_cls .__annotations__ [p ] = a
261+ return _
188262 return decorator
0 commit comments