@@ -20,10 +20,17 @@ class NotAProcessClassError(ValueError):
2020 pass
2121
2222
23- def ensure_process_decorated (cls ):
24- if not getattr (cls , "__xsimlab_process__" , False ):
25- raise NotAProcessClassError ("{cls!r} is not a "
26- "process-decorated class." .format (cls = cls ))
23+ def _get_embedded_process_cls (cls ):
24+ if getattr (cls , "__xsimlab_process__" , False ):
25+ return cls
26+
27+ else :
28+ try :
29+ return cls .__xsimlab_cls__
30+ except AttributeError :
31+ raise NotAProcessClassError ("{cls!r} is not a "
32+ "process-decorated class."
33+ .format (cls = cls ))
2734
2835
2936def get_process_cls (obj_or_cls ):
@@ -32,22 +39,16 @@ def get_process_cls(obj_or_cls):
3239 else :
3340 cls = obj_or_cls
3441
35- ensure_process_decorated (cls )
36-
37- return cls
42+ return _get_embedded_process_cls (cls )
3843
3944
4045def get_process_obj (obj_or_cls ):
4146 if inspect .isclass (obj_or_cls ):
4247 cls = obj_or_cls
43- obj = cls ()
4448 else :
4549 cls = type (obj_or_cls )
46- obj = obj_or_cls
4750
48- ensure_process_decorated (cls )
49-
50- return obj
51+ return _get_embedded_process_cls (cls )()
5152
5253
5354def filter_variables (process , var_type = None , intent = None , group = None ,
@@ -137,46 +138,6 @@ def get_target_variable(var):
137138 return target_process_cls , target_var
138139
139140
140- def _attrify_class (cls ):
141- """Return a `cls` after having passed through :func:`attr.attrs`.
142-
143- This pulls out and converts `attr.ib` declared as class attributes
144- into :class:`attr.Attribute` objects and it also adds
145- dunder-methods such as `__init__`.
146-
147- The following instance attributes are also defined with None or
148- empty values (proper values will be set later at model creation):
149-
150- __xsimlab_model__ : obj
151- :class:`Model` instance to which the process instance is attached.
152- __xsimlab_name__ : str
153- Name given for this process in the model.
154- __xsimlab_store__ : dict or object
155- Simulation data store.
156- __xsimlab_store_keys__ : dict
157- Dictionary that maps variable names to their corresponding key
158- (or list of keys for group variables) in the store.
159- Such keys consist of pairs like `('foo', 'bar')` where
160- 'foo' is the name of any process in the same model and 'bar' is
161- the name of a variable declared in that process.
162- __xsimlab_od_keys__ : dict
163- Dictionary that maps variable names to the location of their target
164- on-demand variable (or a list of locations for group variables).
165- Locations are tuples like store keys.
166-
167- """
168- def init_process (self ):
169- self .__xsimlab_model__ = None
170- self .__xsimlab_name__ = None
171- self .__xsimlab_store__ = None
172- self .__xsimlab_store_keys__ = {}
173- self .__xsimlab_od_keys__ = {}
174-
175- setattr (cls , '__attrs_post_init__' , init_process )
176-
177- return attr .attrs (cls )
178-
179-
180141def _make_property_variable (var ):
181142 """Create a property for a variable or a foreign variable (after
182143 some sanity checks).
@@ -400,11 +361,42 @@ def execute(self, obj, stage, runtime_context):
400361 return executor .execute (obj , runtime_context )
401362
402363
364+ def _process_cls_init (obj ):
365+ """Set the following instance attributes with None or empty values
366+ (proper values will be set later at model creation):
367+
368+ __xsimlab_model__ : obj
369+ :class:`Model` instance to which the process instance is attached.
370+ __xsimlab_name__ : str
371+ Name given for this process in the model.
372+ __xsimlab_store__ : dict or object
373+ Simulation data store.
374+ __xsimlab_store_keys__ : dict
375+ Dictionary that maps variable names to their corresponding key
376+ (or list of keys for group variables) in the store.
377+ Such keys consist of pairs like `('foo', 'bar')` where
378+ 'foo' is the name of any process in the same model and 'bar' is
379+ the name of a variable declared in that process.
380+ __xsimlab_od_keys__ : dict
381+ Dictionary that maps variable names to the location of their target
382+ on-demand variable (or a list of locations for group variables).
383+ Locations are tuples like store keys.
384+
385+ """
386+ obj .__xsimlab_model__ = None
387+ obj .__xsimlab_name__ = None
388+ obj .__xsimlab_store__ = None
389+ obj .__xsimlab_store_keys__ = {}
390+ obj .__xsimlab_od_keys__ = {}
391+
392+
403393class _ProcessBuilder :
404- """Used to iteratively create a new process class.
394+ """Used to iteratively create a new process class from an existing
395+ "dataclass", i.e., a class decorated with ``attr.attrs``.
405396
406- The original class must be already "attr-yfied", i.e., it must
407- correspond to a class returned by `attr.attrs`.
397+ The process class is a direct child of the given dataclass, with
398+ attributes (fields) redefined and properties created so that it
399+ can be used within a model.
408400
409401 """
410402 _make_prop_funcs = {
@@ -415,32 +407,59 @@ class _ProcessBuilder:
415407 }
416408
417409 def __init__ (self , attr_cls ):
418- self ._cls = attr_cls
419- self ._cls .__xsimlab_process__ = True
420- self ._cls .__xsimlab_executor__ = _ProcessExecutor (self ._cls )
421- self ._cls_dict = {}
410+ self ._base_cls = attr_cls
411+ self ._p_cls_dict = {}
422412
423- def add_properties (self , var_type ):
424- make_prop_func = self . _make_prop_funcs [ var_type ]
413+ def _reset_attributes (self ):
414+ new_attributes = OrderedDict ()
425415
426- for var_name , var in filter_variables (self ._cls , var_type ).items ():
427- self ._cls_dict [var_name ] = make_prop_func (var )
416+ for k , attrib in attr .fields_dict (self ._base_cls ).items ():
417+ new_attributes [k ] = attr .attrib (
418+ metadata = attrib .metadata ,
419+ validator = attrib .validator ,
420+ default = attr .NOTHING ,
421+ init = False ,
422+ cmp = False ,
423+ repr = False
424+ )
428425
429- def add_repr (self ):
430- self ._cls_dict ['__repr__' ] = repr_process
426+ return new_attributes
427+
428+ def _make_process_subclass (self ):
429+ p_cls = attr .make_class (self ._base_cls .__name__ ,
430+ self ._reset_attributes (),
431+ bases = (self ._base_cls ,),
432+ init = False ,
433+ repr = False )
434+
435+ setattr (p_cls , '__init__' , _process_cls_init )
436+ setattr (p_cls , '__repr__' , repr_process )
437+ setattr (p_cls , '__xsimlab_process__' , True )
438+ setattr (p_cls , '__xsimlab_executor__' , _ProcessExecutor (p_cls ))
439+
440+ return p_cls
441+
442+ def add_properties (self ):
443+ for var_name , var in attr .fields_dict (self ._base_cls ).items ():
444+ var_type = var .metadata .get ('var_type' )
445+
446+ if var_type is not None :
447+ make_prop_func = self ._make_prop_funcs [var_type ]
448+
449+ self ._p_cls_dict [var_name ] = make_prop_func (var )
431450
432451 def render_docstrings (self ):
433- # self._cls_dict ['__doc__'] = "Process-ified class."
452+ # self._p_cls_dict ['__doc__'] = "Process-ified class."
434453 raise NotImplementedError ("autodoc is not yet implemented." )
435454
436455 def build_class (self ):
437- cls = self ._cls
456+ p_cls = self ._make_process_subclass ()
438457
439458 # Attach properties (and docstrings)
440- for name , value in self ._cls_dict .items ():
441- setattr (cls , name , value )
459+ for name , value in self ._p_cls_dict .items ():
460+ setattr (p_cls , name , value )
442461
443- return cls
462+ return p_cls
444463
445464
446465def process (maybe_cls = None , autodoc = False ):
@@ -475,19 +494,18 @@ def process(maybe_cls=None, autodoc=False):
475494
476495 """
477496 def wrap (cls ):
478- attr_cls = _attrify_class (cls )
497+ attr_cls = attr . attrs (cls )
479498
480499 builder = _ProcessBuilder (attr_cls )
481500
482- for var_type in VarType :
483- builder .add_properties (var_type )
501+ builder .add_properties ()
484502
485503 if autodoc :
486504 builder .render_docstrings ()
487505
488- builder .add_repr ( )
506+ setattr ( attr_cls , '__xsimlab_cls__' , builder .build_class () )
489507
490- return builder . build_class ()
508+ return attr_cls
491509
492510 if maybe_cls is None :
493511 return wrap
0 commit comments