@@ -20,10 +20,17 @@ class NotAProcessClassError(ValueError):
20
20
pass
21
21
22
22
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 ))
27
34
28
35
29
36
def get_process_cls (obj_or_cls ):
@@ -32,22 +39,16 @@ def get_process_cls(obj_or_cls):
32
39
else :
33
40
cls = obj_or_cls
34
41
35
- ensure_process_decorated (cls )
36
-
37
- return cls
42
+ return _get_embedded_process_cls (cls )
38
43
39
44
40
45
def get_process_obj (obj_or_cls ):
41
46
if inspect .isclass (obj_or_cls ):
42
47
cls = obj_or_cls
43
- obj = cls ()
44
48
else :
45
49
cls = type (obj_or_cls )
46
- obj = obj_or_cls
47
50
48
- ensure_process_decorated (cls )
49
-
50
- return obj
51
+ return _get_embedded_process_cls (cls )()
51
52
52
53
53
54
def filter_variables (process , var_type = None , intent = None , group = None ,
@@ -137,46 +138,6 @@ def get_target_variable(var):
137
138
return target_process_cls , target_var
138
139
139
140
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
-
180
141
def _make_property_variable (var ):
181
142
"""Create a property for a variable or a foreign variable (after
182
143
some sanity checks).
@@ -400,11 +361,42 @@ def execute(self, obj, stage, runtime_context):
400
361
return executor .execute (obj , runtime_context )
401
362
402
363
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
+
403
393
class _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``.
405
396
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.
408
400
409
401
"""
410
402
_make_prop_funcs = {
@@ -415,32 +407,59 @@ class _ProcessBuilder:
415
407
}
416
408
417
409
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 = {}
422
412
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 ()
425
415
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
+ )
428
425
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 )
431
450
432
451
def render_docstrings (self ):
433
- # self._cls_dict ['__doc__'] = "Process-ified class."
452
+ # self._p_cls_dict ['__doc__'] = "Process-ified class."
434
453
raise NotImplementedError ("autodoc is not yet implemented." )
435
454
436
455
def build_class (self ):
437
- cls = self ._cls
456
+ p_cls = self ._make_process_subclass ()
438
457
439
458
# 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 )
442
461
443
- return cls
462
+ return p_cls
444
463
445
464
446
465
def process (maybe_cls = None , autodoc = False ):
@@ -475,19 +494,18 @@ def process(maybe_cls=None, autodoc=False):
475
494
476
495
"""
477
496
def wrap (cls ):
478
- attr_cls = _attrify_class (cls )
497
+ attr_cls = attr . attrs (cls )
479
498
480
499
builder = _ProcessBuilder (attr_cls )
481
500
482
- for var_type in VarType :
483
- builder .add_properties (var_type )
501
+ builder .add_properties ()
484
502
485
503
if autodoc :
486
504
builder .render_docstrings ()
487
505
488
- builder .add_repr ( )
506
+ setattr ( attr_cls , '__xsimlab_cls__' , builder .build_class () )
489
507
490
- return builder . build_class ()
508
+ return attr_cls
491
509
492
510
if maybe_cls is None :
493
511
return wrap
0 commit comments