engforge 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. engforge/__init__.py +26 -0
  2. engforge/_testing_components.py +864 -0
  3. engforge/analysis.py +257 -0
  4. engforge/attr_dynamics.py +222 -0
  5. engforge/attr_plotting.py +811 -0
  6. engforge/attr_signals.py +116 -0
  7. engforge/attr_slots.py +211 -0
  8. engforge/attr_solver.py +443 -0
  9. engforge/attributes.py +326 -0
  10. engforge/common.py +76 -0
  11. engforge/component_collections.py +276 -0
  12. engforge/components.py +55 -0
  13. engforge/configuration.py +673 -0
  14. engforge/dataframe.py +185 -0
  15. engforge/datastores/__init__.py +116 -0
  16. engforge/datastores/data.py +519 -0
  17. engforge/datastores/gdocs.py +2650 -0
  18. engforge/datastores/reporting.py +935 -0
  19. engforge/datastores/secrets.py +549 -0
  20. engforge/dynamics.py +662 -0
  21. engforge/eng/__init__.py +0 -0
  22. engforge/eng/costs.py +1459 -0
  23. engforge/eng/fluid_material.py +279 -0
  24. engforge/eng/geometry.py +1060 -0
  25. engforge/eng/pipes.py +1211 -0
  26. engforge/eng/prediction.py +366 -0
  27. engforge/eng/solid_materials.py +445 -0
  28. engforge/eng/structure.py +2228 -0
  29. engforge/eng/structure_beams.py +821 -0
  30. engforge/eng/thermodynamics.py +271 -0
  31. engforge/engforge_attributes.py +380 -0
  32. engforge/env_var.py +214 -0
  33. engforge/locations.py +18 -0
  34. engforge/logging.py +290 -0
  35. engforge/patterns.py +449 -0
  36. engforge/problem_context.py +2251 -0
  37. engforge/properties.py +501 -0
  38. engforge/reporting.py +595 -0
  39. engforge/solveable.py +964 -0
  40. engforge/solver.py +316 -0
  41. engforge/solver_utils.py +726 -0
  42. engforge/system.py +143 -0
  43. engforge/system_reference.py +299 -0
  44. engforge/tabulation.py +288 -0
  45. engforge/test/__init__.py +2 -0
  46. engforge/test/_pre_test_structures.py +221 -0
  47. engforge/test/report_testing.py +158 -0
  48. engforge/test/test_airfilter.py +91 -0
  49. engforge/test/test_analysis.py +3 -0
  50. engforge/test/test_comp_iter.py +196 -0
  51. engforge/test/test_composition.py +139 -0
  52. engforge/test/test_costs.py +338 -0
  53. engforge/test/test_dynamics.py +109 -0
  54. engforge/test/test_dynamics_spaces.py +39 -0
  55. engforge/test/test_four_bar.py +97 -0
  56. engforge/test/test_modules.py +72 -0
  57. engforge/test/test_performance.py +93 -0
  58. engforge/test/test_pipes.py +49 -0
  59. engforge/test/test_problem.py +411 -0
  60. engforge/test/test_problem_deepscoping.py +68 -0
  61. engforge/test/test_slider_crank.py +51 -0
  62. engforge/test/test_solver.py +343 -0
  63. engforge/test/test_tabulation.py +233 -0
  64. engforge/typing.py +92 -0
  65. engforge-0.3.0.dist-info/METADATA +392 -0
  66. engforge-0.3.0.dist-info/RECORD +69 -0
  67. engforge-0.3.0.dist-info/WHEEL +5 -0
  68. engforge-0.3.0.dist-info/licenses/LICENSE +7 -0
  69. engforge-0.3.0.dist-info/top_level.txt +1 -0
engforge/attributes.py ADDED
@@ -0,0 +1,326 @@
1
+ """Defines a customizeable attrs attribute that is handled in configuration,
2
+
3
+ on init an instance of `Instance` type for any ATTR_BASE subclass is created"""
4
+
5
+ import attrs, attr, uuid
6
+ from engforge.logging import LoggingMixin, log
7
+
8
+
9
+ class ATTRLog(LoggingMixin):
10
+ pass
11
+
12
+
13
+ log = ATTRLog()
14
+
15
+
16
+ DEFAULT_COMBO = "default"
17
+
18
+
19
+ class AttributeInstance:
20
+ class_attr: "ATTR_BASE"
21
+ system: "System"
22
+ classname: str = "attribute" # for ref compatability
23
+ backref: "ATTR_BASE"
24
+
25
+ # TODO: universal slots method
26
+ # __slots__ = ["system", "class_attr"]
27
+
28
+ def __init__(self, class_attr: "CLASS_ATTR", system: "System", **kwargs) -> None:
29
+ self.class_attr = class_attr
30
+ self.system = system
31
+ self.compile(**kwargs)
32
+
33
+ def compile(self, **kwargs):
34
+ # raise NotImplementedError("Override Me!")
35
+ pass
36
+
37
+ def as_ref_dict(self) -> dict:
38
+ log.info(f"pass - as ref {self}")
39
+ return None
40
+
41
+ def get_alias(self, path):
42
+ return path.split(".")[-1]
43
+
44
+ def is_active(self, value=False) -> bool:
45
+ mthd = "dflt"
46
+ if hasattr(self, "_active"):
47
+ mthd = "instance"
48
+ value = self._active
49
+
50
+ elif hasattr(self.class_attr, "active"):
51
+ mthd = "class"
52
+ value = self.class_attr.active
53
+
54
+ # print(f'{self}| {mthd} is_active: {value}')
55
+ # the default
56
+ return value
57
+
58
+ @property
59
+ def combos(self):
60
+ return self.class_attr.combos
61
+
62
+ @property
63
+ def active(self):
64
+ return self.class_attr.active
65
+
66
+
67
+ # TODO: add a concept of queries of ATTR_BASE subclasses, where multiple types may be selected or filtered, with individual arguments.
68
+ class ATTR_BASE(attrs.Attribute):
69
+ """A base class that handles initalization in the attrs meta class scheme by ultimately createing an Instance"""
70
+
71
+ name: str
72
+ config_cls: "System"
73
+ attr_prefix = "ATTR"
74
+ instance_class: AttributeInstance = AttributeInstance # Define me
75
+ default_options: dict
76
+ template_class = True
77
+
78
+ # TODO: add generic selection & activation of attributes
79
+ active: bool
80
+ combos: list
81
+
82
+ none_ok = False
83
+
84
+ # Activation & Combo Selection Functionality
85
+ @classmethod
86
+ def process_combos(cls, combos, default=None, add_combos=None):
87
+ if isinstance(combos, str):
88
+ if "*" in combos:
89
+ raise KeyError("wildcard (*) not allowed in combos!")
90
+ out = combos.split(",")
91
+ elif isinstance(combos, list):
92
+ if any(["*" in c for c in combos]):
93
+ raise KeyError("wildcard (*) not allowed in combos!")
94
+ out = combos
95
+ else:
96
+ out = []
97
+
98
+ if add_combos is not None:
99
+ out += cls.process_combos(add_combos, default=default)
100
+
101
+ if out:
102
+ return out
103
+ if default:
104
+ return default
105
+ return ["default"]
106
+
107
+ # Initialization
108
+ @classmethod
109
+ def configure_for_system(cls, name, config_class, cb=None, **kwargs):
110
+ """add the config class, and perform checks with `class_validate)
111
+ :returns: [optional] a dictionary of options to be used in the make_attribute method
112
+ """
113
+ log.debug(f"{cls.__name__} is being configured for {cls.attr_prefix}")
114
+ cls.name = name
115
+ cls.config_cls = config_class
116
+
117
+ if isinstance(cls.instance_class, ATTR_BASE) and not hasattr(
118
+ cls.instance_class, "backref"
119
+ ):
120
+ # get parent class
121
+ refs = [cls, ATTR_BASE]
122
+ mro_group = cls.mro()
123
+ mro_group = mro_group[: mro_group.index(ATTR_BASE)]
124
+ cans = [v for v in mro_group if v not in refs]
125
+ last = cans[-1]
126
+ # if not hasattr(cls,'instance_class') or not cls.instance_class:
127
+ # log.info(f'no instance class! {cls}')
128
+ cls.instance_class.backref = last
129
+ else:
130
+ pass
131
+ # print(f'backref exists {cls.instance_class.backref} {cls}')
132
+
133
+ if cb is not None:
134
+ cb(cls, config_class, **kwargs)
135
+
136
+ return {} # OVERWRITE ME "custom_options":False
137
+
138
+ @classmethod
139
+ def create_instance(cls, instance: "Configuration") -> AttributeInstance:
140
+ """Create an instance of the instance_class"""
141
+ if cls.instance_class is None:
142
+ raise Exception(
143
+ f"Instance Class Hasnt Been Defined For `{cls}.instance_class`"
144
+ )
145
+ if not hasattr(cls, "config_cls"):
146
+ raise Exception(f"`config_cls` hasnt been defined for `{cls}`")
147
+
148
+ cls.class_validate(instance=instance)
149
+ return cls.instance_class(cls, instance)
150
+
151
+ # Override Me:
152
+ @classmethod
153
+ def configure_instance(cls, instance, attribute, value):
154
+ """validates the instance given attr's init routine"""
155
+ pass
156
+
157
+ @classmethod
158
+ def class_validate(cls, instance, **kwargs):
159
+ """validates onetime A method to validate the kwargs passed to the define method"""
160
+ pass
161
+
162
+ @classmethod
163
+ def define_validate(cls, **kwargs):
164
+ """A method to validate the kwargs passed to the define method"""
165
+ pass
166
+
167
+ # Interafce & Utility
168
+ @classmethod
169
+ def define(cls, **kwargs):
170
+ """taking a component or system class as possible input valid input is later validated as an instance of that class or subclass"""
171
+ assert (
172
+ not cls.template_class
173
+ ), f"{cls} is not template class and cannot defined anything, ie anything that has been created as a function of `define` cannot be defined"
174
+
175
+ cls.define_validate(**kwargs)
176
+
177
+ # Create A New Signals Class
178
+ kw_pairs = [f"{k}_{v}" for k, v in kwargs.items()]
179
+ new_name = f"{cls.attr_prefix}_{('_'.join(kw_pairs))}".replace(".", "_")
180
+ # define the class dictionary
181
+ new_dict = dict(name=new_name)
182
+ new_slot = cls._setup_cls(new_name, new_dict, **kwargs)
183
+ return new_slot
184
+
185
+ @classmethod
186
+ def _setup_cls(cls, name, new_dict, **kwargs):
187
+ # randomize name for specifics reasons
188
+ uid = str(uuid.uuid4())
189
+ # base info
190
+ name = name + "_" + uid.replace("-", "")[0:16]
191
+ new_dict["uuid"] = uid
192
+ new_dict["default_options"] = cls.default_options.copy()
193
+ new_dict["template_class"] = False
194
+ new_dict["name"] = name
195
+
196
+ # create a new type of attribute
197
+ new_slot = type(name, (cls,), new_dict)
198
+ new_slot.default_options["default"] = new_slot.make_factory()
199
+ new_slot.default_options["validator"] = new_slot.configure_instance
200
+ log.debug(
201
+ f"defined {name}|{new_slot} with {kwargs}| {new_slot.default_options}"
202
+ )
203
+ return new_slot
204
+
205
+ @classmethod
206
+ def make_factory(cls, **kwargs):
207
+ # print(f"{cls} making factory with: {kwargs}")
208
+ return attrs.Factory(cls.create_instance, takes_self=True)
209
+
210
+ @classmethod
211
+ def make_attribute(cls, name, comp_class, **kwargs):
212
+ """makes an attrs.Attribute for the class"""
213
+ cust_options = cls.configure_for_system(name, comp_class, **kwargs)
214
+ # make copy for new instance
215
+ opts = cls.default_options.copy()
216
+ # update with custom kwargs
217
+ if isinstance(cust_options, dict):
218
+ opts.update(cust_options)
219
+ # input has the final override
220
+ opts.update(kwargs)
221
+ # The core functionality
222
+ opts.update(name=name, type=cls)
223
+
224
+ # Handle the default and validator, if not overridden
225
+ if "default" not in kwargs:
226
+ opts["default"] = cls.make_factory()
227
+ if "validator" not in kwargs:
228
+ opts["validator"] = cls.configure_instance
229
+
230
+ return attrs.Attribute(**opts)
231
+
232
+ # Structural Orchestration Through Subclassing
233
+ @classmethod
234
+ def collect_cls(cls, system) -> dict:
235
+ """collects all the attributes for a system"""
236
+ if not isinstance(system, type):
237
+ system = system.__class__
238
+ return {k: at.type for k, at in system._get_init_attrs_data(cls).items()}
239
+
240
+ @classmethod
241
+ def collect_attr_inst(cls, system, handle_inst=True) -> dict:
242
+ """collects all the attribute instances for a system"""
243
+ cattr = cls.collect_cls(system)
244
+ out = {}
245
+ for k, v in cattr.items():
246
+ inst = getattr(system, k)
247
+ if (inst is None and getattr(cls.instance_class, "none_ok", False)) or (
248
+ cls.instance_class is not None
249
+ and not isinstance(inst, cls.instance_class)
250
+ ):
251
+ log.warning(
252
+ f"Attribute {k}|{inst} is not an instance of {cls.instance_class} in {system}"
253
+ )
254
+ continue
255
+
256
+ if inst and handle_inst:
257
+ inst = cls.handle_instance(inst)
258
+
259
+ if inst is not None:
260
+ out[k] = inst
261
+ return out
262
+
263
+ @staticmethod
264
+ def unpack_atrs(d, pre="", conv=None):
265
+ if not conv:
266
+ conv = lambda v: v
267
+ if isinstance(d, dict):
268
+ for k, v in d.items():
269
+ if not isinstance(v, dict):
270
+ yield k, pre, conv(v)
271
+ elif v:
272
+ if pre and not pre.endswith("."):
273
+ ppre = pre + "."
274
+ ck = f"{ppre}{k}"
275
+ for kii, ki, vi in ATTR_BASE.unpack_atrs(v, ck):
276
+ yield kii, ki, conv(vi)
277
+ else:
278
+ if pre and not pre.endswith("."):
279
+ ppre = pre + "."
280
+ ck = f"{ppre}{k}"
281
+ yield "", ck, v
282
+ return d
283
+
284
+ @classmethod
285
+ def handle_instance(cls, canidate):
286
+ """handles the instance, override as you wish"""
287
+ # print('handle_instance',cls,canidate,cls.instance_class)
288
+
289
+ if isinstance(canidate, cls.instance_class):
290
+ o = canidate.as_ref_dict()
291
+ return o
292
+ return canidate # live and let live
293
+
294
+ @classmethod
295
+ def subclasses(cls, out=None):
296
+ """return all subclasses of components, including their subclasses
297
+ :param out: out is to pass when the middle of a recursive operation, do not use it!
298
+ """
299
+
300
+ # init the set by default
301
+ if out is None:
302
+ out = set()
303
+
304
+ for cls in cls.__subclasses__():
305
+ if not cls.template_class:
306
+ continue
307
+ out.add(cls)
308
+ cls.subclasses(out)
309
+
310
+ return out
311
+
312
+
313
+ ATTR_BASE.default_options = dict(
314
+ # validator=ATTR_BASE.configure_instance,
315
+ repr=False,
316
+ cmp=None,
317
+ hash=None,
318
+ init=False, # change this to allow for init input
319
+ metadata=None,
320
+ converter=None,
321
+ kw_only=True,
322
+ eq=None,
323
+ order=None,
324
+ on_setattr=None,
325
+ inherited=False,
326
+ )
engforge/common.py ADDED
@@ -0,0 +1,76 @@
1
+ """A set of common values and functions that are globaly available."""
2
+
3
+ import sys
4
+ import functools
5
+
6
+ # Common Modules
7
+ from engforge.configuration import *
8
+ from engforge.logging import LoggingMixin
9
+ from urllib.request import urlopen
10
+
11
+ import numpy
12
+
13
+
14
+ class ForgeLog(LoggingMixin):
15
+ pass
16
+
17
+
18
+ log = ForgeLog()
19
+ log.info("Starting engforge Enviornment")
20
+
21
+
22
+ class inst_vectorize(numpy.vectorize):
23
+ def __get__(self, obj, objtype):
24
+ return functools.partial(self.__call__, obj)
25
+
26
+
27
+ def chunks(lst, n):
28
+ """Yield successive n-sized chunks from lst."""
29
+ for i in range(0, len(lst), n):
30
+ yield lst[i : i + n]
31
+
32
+
33
+ def is_ec2_instance():
34
+ """Check if an instance is running on AWS."""
35
+ result = False
36
+ meta = "http://169.254.169.254/latest/meta-data/public-ipv4"
37
+ try:
38
+ result = urlopen(meta, timeout=5.0).status == 200
39
+ return True
40
+ except:
41
+ return False
42
+ return False
43
+
44
+
45
+ def get_size(obj, seen=None):
46
+ """Recursively finds size of objects"""
47
+ size = sys.getsizeof(obj)
48
+ if seen is None:
49
+ seen = set()
50
+ obj_id = id(obj)
51
+ if obj_id in seen:
52
+ return 0
53
+ # Important mark as seen *before* entering recursion to gracefully handle
54
+ # self-referential objects
55
+ seen.add(obj_id)
56
+ if isinstance(obj, dict):
57
+ size += sum([get_size(v, seen) for v in obj.values()])
58
+ size += sum([get_size(k, seen) for k in obj.keys()])
59
+ elif hasattr(obj, "__dict__"):
60
+ size += get_size(obj.__dict__, seen)
61
+ elif hasattr(obj, "__iter__") and not isinstance(obj, (str, bytes, bytearray)):
62
+ size += sum([get_size(i, seen) for i in obj])
63
+ return size
64
+
65
+
66
+ # Constants
67
+ g = gravity = 9.80665 # m/s2
68
+ G_grav_constant = 6.67430e-11 # m3/kgs
69
+ speed_of_light = 299792458 # m/s
70
+ u_planck = 6.62607015e-34 # Js
71
+ R_univ_gas = 8.314462618 # J/molkg
72
+ mass_electron = 9.1093837015e-31 # kg
73
+ mass_proton = 1.67262192369e-27
74
+ mass_neutron = 1.67492749804e-27
75
+ r_electron = 2.8179403262e-15
76
+ Kboltzman = 1.380649e-23 # J⋅K−1
@@ -0,0 +1,276 @@
1
+ """define a collection of components that will propigate to its parents dataframe
2
+
3
+ When `wide` is set each component's references are reported to the system's table, otherwise only one component's references are reported, however the system will iterate over the components by calling `system.iterable_components`
4
+
5
+ Define a Iterable Component slot in a system by calling `Slot.define_iterable(...,wide=True/False)`
6
+
7
+ CostModel isonly supported in wide mode at this time.
8
+
9
+ Types:
10
+ 1. ComponentList, ordered by index
11
+ 2. ComponentDict, ordered by key
12
+ 3. ComponentGraph, ?#TODO:
13
+ """
14
+
15
+ from collections import UserDict, UserList
16
+ from engforge.components import Component
17
+ from engforge.attr_slots import Slot
18
+ from engforge.configuration import forge
19
+ from engforge.typing import *
20
+ from engforge.system_reference import Ref, system_property
21
+ from engforge.properties import *
22
+
23
+
24
+ import attrs
25
+
26
+
27
+ def check_comp_type(instance, attr, value):
28
+ """ensures the input component type is a Component"""
29
+ from engforge.eng.costs import CostModel
30
+
31
+ if not instance.wide and isinstance(value, type) and issubclass(value, CostModel):
32
+ raise TypeError(f"Cost Mixin Not Supported As Iter Type! {value}")
33
+
34
+ if isinstance(value, type) and issubclass(value, Component):
35
+ return
36
+
37
+ raise TypeError(f"Not A Component Class! {value}")
38
+
39
+
40
+ class iter_tkn:
41
+ """ambigious type to keep track of iterable position form system reference"""
42
+
43
+ pass
44
+
45
+
46
+ @forge
47
+ class ComponentIter(Component):
48
+ """Iterable components are designed to eval a large selection of components either one-by-one or all at once at the system level depending on if `wide` property is set."""
49
+
50
+ _ref_cache: dict = None
51
+ _item_refs: dict = None
52
+
53
+ wide: bool = True
54
+
55
+ # what holds the components
56
+ data: iter
57
+
58
+ # current item keu, non table type, this triggers `anything_changed` in `system._iterate_component()`
59
+ current_item: iter_tkn = attr.ib(factory=lambda: None, hash=False, eq=False)
60
+ _first_item_key: iter_tkn
61
+
62
+ @property
63
+ def current(self):
64
+ """returns all data in wide format and the active key in _current_item"""
65
+ if self.wide:
66
+ return self.data
67
+ else:
68
+ if self.current_item is None:
69
+ if not hasattr(self, "_first_item_key"):
70
+ next(self._item_gen())
71
+ return self.data[self._first_item_key]
72
+ return self.data[self.current_item]
73
+
74
+ def _item_gen(self):
75
+ for key, item in self.data.items():
76
+ if not hasattr(self, "_first_item_key"):
77
+ self._first_item_key = key
78
+ yield key, item
79
+
80
+ def __setitem__(self, key, value):
81
+ assert isinstance(
82
+ value, self.component_type
83
+ ), f"{value} is not of type: {self.component_type}"
84
+ super().__setitem__(key, value)
85
+ self.reset()
86
+
87
+ def reset(self):
88
+ # reset reference cache
89
+ self._prv_internal_references = None
90
+ self._item_refs = None
91
+ self.current_item = None
92
+
93
+ def _item_key(self, itkey, item):
94
+ """override this to customize data access to self.data or other container name"""
95
+ return itkey
96
+
97
+ # TODO: way to update internal references in problem, do on update()?
98
+ @property
99
+ def _internal_references(self) -> dict:
100
+ """considers wide format to return active references"""
101
+ if self.wide:
102
+ return self._prv_internal_references
103
+ else:
104
+ if self.current_item is None:
105
+ return self._item_refs[self._first_item_key]
106
+ return self._item_refs[self.current_item]
107
+
108
+ def comp_references(self, **kw):
109
+ """Returns this components global references"""
110
+ out = {}
111
+ out["attributes"] = at = {}
112
+ out["properties"] = pr = {}
113
+
114
+ for key in self.system_properties_classdef():
115
+ pr[key] = Ref(self, key, True, False)
116
+
117
+ for key in self.input_fields():
118
+ at[key] = Ref(self, key, False, True)
119
+
120
+ return out
121
+
122
+ def internal_references(self, recache=False, numeric_only=False):
123
+ """lists the this_name.comp_key.<attr/prop key>: Ref format to override data_dict"""
124
+
125
+ if (
126
+ recache == False
127
+ and hasattr(self, "_prv_internal_references")
128
+ and self._prv_internal_references
129
+ ):
130
+ return self._internal_references
131
+
132
+ keeprefcopy = lambda d: {k: {**c} for k, c in d.items()}
133
+
134
+ out = keeprefcopy(self.comp_references())
135
+ at = out["attributes"] # = at = {}
136
+ pr = out["properties"] # = pr = {}
137
+ _item_refs = {}
138
+
139
+ for itkey, item in self._item_gen():
140
+ it_base_key = self._item_key(itkey, item)
141
+
142
+ _item_refs[itkey] = ir = keeprefcopy(self.comp_references())
143
+ atr = ir["attributes"] # = atr = {}
144
+ prr = ir["properties"] # = prr = {}
145
+
146
+ # set property refs
147
+ for key in item.system_properties_classdef():
148
+ k = f"{it_base_key}.{key}"
149
+ rc = Ref(item, key, True, False)
150
+ pr[k] = rc
151
+ prr[key] = rc
152
+
153
+ # set attr refs
154
+ if numeric_only:
155
+ for key in item.numeric_fields():
156
+ k = f"{it_base_key}.{key}"
157
+ ri = Ref(item, key, False, True)
158
+ at[k] = ri
159
+ atr[key] = ri
160
+
161
+ else:
162
+ for key in item.input_fields():
163
+ k = f"{it_base_key}.{key}"
164
+ ri = Ref(item, key, False, True)
165
+ at[k] = ri
166
+ atr[key] = ri
167
+
168
+ # cache the references
169
+ self._prv_internal_references = out
170
+ self._item_refs = _item_refs
171
+ return self._internal_references
172
+
173
+ def __hash__(self):
174
+ return hash(id(self))
175
+
176
+
177
+ @forge
178
+ class ComponentDict(ComponentIter, UserDict):
179
+ """Stores components by name, and allows tabulation of them"""
180
+
181
+ component_type: type = attrs.field(validator=check_comp_type)
182
+
183
+ # tabulate_depth:int = attrs.field(default=1) #TODO: impement this
184
+
185
+ _ref_cache: dict = None
186
+
187
+ # Dict Setup
188
+ def __on_init__(self):
189
+ UserDict.__init__(self)
190
+
191
+ def __str__(self):
192
+ return f"{self.__class__.__name__}[{len(self.data)}]"
193
+
194
+ def __repr__(self) -> str:
195
+ return str(self)
196
+
197
+
198
+ @forge
199
+ class ComponentIterator(ComponentIter, UserList):
200
+ """Stores components by name, and allows tabulation of them"""
201
+
202
+ component_type: type = attrs.field(validator=check_comp_type)
203
+
204
+ # Dict Setup
205
+ def __on_init__(self):
206
+ UserList.__init__(self)
207
+
208
+ def _item_gen(self):
209
+ for i, item in enumerate(self.data):
210
+ if not hasattr(self, "_first_item_key"):
211
+ self._first_item_key = i
212
+ yield i, item
213
+
214
+ def _item_key(self, itkey, item):
215
+ return f"{self.component_type.__name__.lower()}.{itkey}"
216
+
217
+ # def __setitem__(self, key, value):
218
+ # assert isinstance(value,self.component_type)
219
+ # super().__setitem__(key, value)
220
+ # #reset reference cache
221
+ # self._ref_cache = None
222
+
223
+
224
+ # #Tabulation Override
225
+ # @property
226
+ # def internal_references(self):
227
+ # """lists the this_name.comp_key.<attr/prop key>: Ref format to override data_dict"""
228
+ #
229
+ # if self._ref_cache:
230
+ # return self._ref_cache
231
+ #
232
+ # out = {}
233
+ # out["attributes"] = at = {}
234
+ # out["properties"] = pr = {}
235
+ #
236
+ # for itkey,item in self._item_gen():
237
+ # it_base_key = f'{itkey}'
238
+ #
239
+ # for key in item.system_properties_classdef():
240
+ # k = f'{it_base_key}.{key}'
241
+ # pr[k] = Ref(item, key)
242
+ #
243
+ # for key in item.input_fields():
244
+ # k = f'{it_base_key}.{key}'
245
+ # at[k] = Ref(item, key, False)
246
+ #
247
+ # #cache the references
248
+ # self._ref_cache = out
249
+ # return out
250
+
251
+ # #Tabulation Override
252
+ # @property
253
+ # def internal_references(self):
254
+ # """lists the this_name.comp_key.<attr/prop key>: Ref format to override data_dict"""
255
+ #
256
+ # if self._ref_cache:
257
+ # return self._ref_cache
258
+ #
259
+ # out = {}
260
+ # out["attributes"] = at = {}
261
+ # out["properties"] = pr = {}
262
+ #
263
+ # for it,item in self._item_gen():
264
+ # it_base_key = f'{self.component_type.__name__.lower()}.{it}'
265
+ #
266
+ # for key in item.system_properties_classdef():
267
+ # k = f'{it_base_key}.{key}'
268
+ # pr[k] = Ref(item, key)
269
+ #
270
+ # for key in item.input_fields():
271
+ # k = f'{it_base_key}.{key}'
272
+ # at[k] = Ref(item, key, False)
273
+ #
274
+ # #cache the references
275
+ # self._ref_cache = out
276
+ # return out