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.
- engforge/__init__.py +26 -0
- engforge/_testing_components.py +864 -0
- engforge/analysis.py +257 -0
- engforge/attr_dynamics.py +222 -0
- engforge/attr_plotting.py +811 -0
- engforge/attr_signals.py +116 -0
- engforge/attr_slots.py +211 -0
- engforge/attr_solver.py +443 -0
- engforge/attributes.py +326 -0
- engforge/common.py +76 -0
- engforge/component_collections.py +276 -0
- engforge/components.py +55 -0
- engforge/configuration.py +673 -0
- engforge/dataframe.py +185 -0
- engforge/datastores/__init__.py +116 -0
- engforge/datastores/data.py +519 -0
- engforge/datastores/gdocs.py +2650 -0
- engforge/datastores/reporting.py +935 -0
- engforge/datastores/secrets.py +549 -0
- engforge/dynamics.py +662 -0
- engforge/eng/__init__.py +0 -0
- engforge/eng/costs.py +1459 -0
- engforge/eng/fluid_material.py +279 -0
- engforge/eng/geometry.py +1060 -0
- engforge/eng/pipes.py +1211 -0
- engforge/eng/prediction.py +366 -0
- engforge/eng/solid_materials.py +445 -0
- engforge/eng/structure.py +2228 -0
- engforge/eng/structure_beams.py +821 -0
- engforge/eng/thermodynamics.py +271 -0
- engforge/engforge_attributes.py +380 -0
- engforge/env_var.py +214 -0
- engforge/locations.py +18 -0
- engforge/logging.py +290 -0
- engforge/patterns.py +449 -0
- engforge/problem_context.py +2251 -0
- engforge/properties.py +501 -0
- engforge/reporting.py +595 -0
- engforge/solveable.py +964 -0
- engforge/solver.py +316 -0
- engforge/solver_utils.py +726 -0
- engforge/system.py +143 -0
- engforge/system_reference.py +299 -0
- engforge/tabulation.py +288 -0
- engforge/test/__init__.py +2 -0
- engforge/test/_pre_test_structures.py +221 -0
- engforge/test/report_testing.py +158 -0
- engforge/test/test_airfilter.py +91 -0
- engforge/test/test_analysis.py +3 -0
- engforge/test/test_comp_iter.py +196 -0
- engforge/test/test_composition.py +139 -0
- engforge/test/test_costs.py +338 -0
- engforge/test/test_dynamics.py +109 -0
- engforge/test/test_dynamics_spaces.py +39 -0
- engforge/test/test_four_bar.py +97 -0
- engforge/test/test_modules.py +72 -0
- engforge/test/test_performance.py +93 -0
- engforge/test/test_pipes.py +49 -0
- engforge/test/test_problem.py +411 -0
- engforge/test/test_problem_deepscoping.py +68 -0
- engforge/test/test_slider_crank.py +51 -0
- engforge/test/test_solver.py +343 -0
- engforge/test/test_tabulation.py +233 -0
- engforge/typing.py +92 -0
- engforge-0.3.0.dist-info/METADATA +392 -0
- engforge-0.3.0.dist-info/RECORD +69 -0
- engforge-0.3.0.dist-info/WHEEL +5 -0
- engforge-0.3.0.dist-info/licenses/LICENSE +7 -0
- engforge-0.3.0.dist-info/top_level.txt +1 -0
engforge/system.py
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""A System is a Configuration that orchestrates dataflow between components, as well as solving systems of equations in the presense of limits, as well as formatting results of each Component into reporting ready dataframe. System's solver behavior is inspired by NASA's numerical propulsion simulation system (NPSS) to solve systems of inequalities in complex systems.
|
|
2
|
+
|
|
3
|
+
Component or other subsystems are added to a System class with `Slots`:
|
|
4
|
+
```
|
|
5
|
+
class CustomSystem(System):
|
|
6
|
+
slot_name = Slot.define(Component,ComponentSubclass,System)
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Component's data flow is established via `SignalS` that are defined:
|
|
10
|
+
```
|
|
11
|
+
class CustomSystem(System):
|
|
12
|
+
signal_name = Signal.define(source_attr_or_property, target_attr)
|
|
13
|
+
control_signal = Signal.define(source_attr_or_property, target_attr,control_with='system.attr or slot.attr`)
|
|
14
|
+
```
|
|
15
|
+
- source_attr: can reference a locally defined slot attribute (a la attr's fields) or any locally defined slot system property
|
|
16
|
+
- target_attr: must be a locally defined slot attribute or system attribute.
|
|
17
|
+
|
|
18
|
+
update description to include solver
|
|
19
|
+
|
|
20
|
+
A system calculates its state upon calling `System.run()`. This executes `pre_execute()` first which will directly update any attributes based on their `Signal` definition between `Slot` components. Once convergence is reached target_attr's are updated in `post_execute()` for cyclic SignalS.
|
|
21
|
+
|
|
22
|
+
If the system encounters a subsystem in its solver routine, the subsystem is evald() and its results used as static in that iteration,ie it isn't included in the system level dependents if cyclic references are found.
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
The solver uses the root or cobla scipy optimizer results on quick references to internal component references. Upon solving the system
|
|
26
|
+
|
|
27
|
+
SignalS can be limited with constrains via `min or max` values on `NumericProperty` which can be numeric values (int or float) or functions taking one argument of the component it is defined on. Additionally signals may take arguments of `min` or `max` which are numeric values or callbacks which take the system instance as an argument.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
import attrs
|
|
31
|
+
|
|
32
|
+
from engforge.properties import *
|
|
33
|
+
from engforge.logging import LoggingMixin
|
|
34
|
+
from engforge.configuration import Configuration, forge
|
|
35
|
+
from engforge.components import SolveableInterface
|
|
36
|
+
from engforge.solver import SolverMixin
|
|
37
|
+
from engforge.attr_plotting import PlottingMixin
|
|
38
|
+
from engforge.dynamics import GlobalDynamics
|
|
39
|
+
|
|
40
|
+
import copy
|
|
41
|
+
import collections
|
|
42
|
+
import typing
|
|
43
|
+
import numpy
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# make a module logger
|
|
47
|
+
class SystemsLog(LoggingMixin):
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
log = SystemsLog()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# NOTE: solver must come before solvable interface since it overrides certain methods
|
|
55
|
+
@forge
|
|
56
|
+
class System(SolverMixin, SolveableInterface, PlottingMixin, GlobalDynamics):
|
|
57
|
+
"""A system defines SlotS for Components, and data flow between them using SignalS
|
|
58
|
+
|
|
59
|
+
The system records all attribues to its subcomponents via system_references with scoped keys to references to set or get attributes, as well as observe system properties. These are cached upon first access in an instance.
|
|
60
|
+
|
|
61
|
+
The table is made up of these system references, allowing low overhead recording of systems with many variables.
|
|
62
|
+
|
|
63
|
+
When solving by default the run(revert=True) call will revert the system state to what it was before the system began.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
# default to nothing
|
|
67
|
+
dynamic_input_vars: list = attrs.field(factory=list)
|
|
68
|
+
dynamic_state_vars: list = attrs.field(factory=list)
|
|
69
|
+
dynamic_output_vars: list = attrs.field(factory=list)
|
|
70
|
+
|
|
71
|
+
_anything_changed_ = True
|
|
72
|
+
_solver_override: bool = (
|
|
73
|
+
False # this comp will run with run_internal_systems when True, otherwise it resolves to global solver behavior, also prevents the solver from reaching into this system
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# Properties!
|
|
77
|
+
@system_property
|
|
78
|
+
def converged(self) -> int:
|
|
79
|
+
if self.last_context is None:
|
|
80
|
+
return None
|
|
81
|
+
return self.last_context.converged
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def _converged(self):
|
|
85
|
+
return self.last_context.converged
|
|
86
|
+
|
|
87
|
+
@_converged.setter
|
|
88
|
+
def _converged(self, inpt):
|
|
89
|
+
self.last_context._converged = inpt
|
|
90
|
+
|
|
91
|
+
@system_property
|
|
92
|
+
def run_id(self) -> int:
|
|
93
|
+
if self.last_context is None:
|
|
94
|
+
return None
|
|
95
|
+
return self.last_context.problem_id
|
|
96
|
+
|
|
97
|
+
@classmethod
|
|
98
|
+
def subclasses(cls, out=None):
|
|
99
|
+
"""
|
|
100
|
+
return all subclasses of components, including their subclasses
|
|
101
|
+
:param out: out is to pass when the middle of a recursive operation, do not use it!
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
# init the set by default
|
|
105
|
+
if out is None:
|
|
106
|
+
out = set()
|
|
107
|
+
|
|
108
|
+
for cls in cls.__subclasses__():
|
|
109
|
+
out.add(cls)
|
|
110
|
+
cls.subclasses(out)
|
|
111
|
+
|
|
112
|
+
return out
|
|
113
|
+
|
|
114
|
+
def clone(self):
|
|
115
|
+
"""returns a clone of this system, often used to iterate the system without affecting the input values at the last convergence step."""
|
|
116
|
+
return copy.deepcopy(self)
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def identity(self):
|
|
120
|
+
if self.run_id:
|
|
121
|
+
return f"{self.name}_{self.run_id}"
|
|
122
|
+
else:
|
|
123
|
+
return f"{self.name}"
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def _anything_changed(self):
|
|
127
|
+
"""looks at internal components as well as flag for anything chagned."""
|
|
128
|
+
if self._anything_changed_:
|
|
129
|
+
return True
|
|
130
|
+
elif any([c.anything_changed for k, c in self.comp_references().items()]):
|
|
131
|
+
return True
|
|
132
|
+
return False
|
|
133
|
+
|
|
134
|
+
@_anything_changed.setter
|
|
135
|
+
def _anything_changed(self, inpt):
|
|
136
|
+
"""allows default functionality with new property system"""
|
|
137
|
+
self.mark_all_comps_changed(inpt)
|
|
138
|
+
|
|
139
|
+
def mark_all_comps_changed(self, inpt: bool):
|
|
140
|
+
"""mark all components as changed, useful for forcing a re-run of the system, or for marking data as saved"""
|
|
141
|
+
self._anything_changed_ = inpt
|
|
142
|
+
for k, c in self.comp_references().items():
|
|
143
|
+
c._anything_changed = inpt
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
"""Module to define the Ref class and utility methods for working with it.
|
|
2
|
+
|
|
3
|
+
Ref's are designed to create adhoc links between the systems & components of the system, and their properties. This is useful for creating optimization problems, and for creating dynamic links between the state and properties of the system. Care must be used on the component objects changing of state, the recommened procedure is to copy your base system to prevent hysterisis and other issues, then push/pop X values through the optimization methods and change state upon success
|
|
4
|
+
|
|
5
|
+
Ref.component can be a Component,System or dictionary for referencing via keyword. Ref.key can be a string or a callable, if callable the ref will be evaluated as the result of the callable (allow_set=False), this renders the component unused, and disables the ability to set the value of the reference.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import attrs
|
|
9
|
+
import inspect
|
|
10
|
+
import numpy as np
|
|
11
|
+
import collections
|
|
12
|
+
import scipy.optimize as sciopt
|
|
13
|
+
from contextlib import contextmanager
|
|
14
|
+
from engforge.properties import *
|
|
15
|
+
import copy
|
|
16
|
+
from difflib import get_close_matches
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RefLog(LoggingMixin):
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
log = RefLog()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def refset_input(refs, delta_dict, chk=True, fail=True, warn=True, scope="ref"):
|
|
27
|
+
"""change a set of refs with a dictionary of values. If chk is True k will be checked for membership in refs"""
|
|
28
|
+
keys = set(refs.keys())
|
|
29
|
+
for k, v in delta_dict.items():
|
|
30
|
+
if isinstance(k, Ref):
|
|
31
|
+
k.set_value(v)
|
|
32
|
+
continue
|
|
33
|
+
|
|
34
|
+
memb = k in refs
|
|
35
|
+
# if a match or not checking go ahead.
|
|
36
|
+
if not chk or memb:
|
|
37
|
+
refs[k].set_value(v)
|
|
38
|
+
|
|
39
|
+
# TODO: handle setting non-ref values such as dictionaries
|
|
40
|
+
|
|
41
|
+
elif fail and chk and not memb:
|
|
42
|
+
close = get_close_matches(k, keys)
|
|
43
|
+
raise KeyError(f"{scope}| key {k} not in refs. did you mean {close}?")
|
|
44
|
+
elif warn and chk and not memb:
|
|
45
|
+
close = get_close_matches(k, keys)
|
|
46
|
+
log.warning(f"{scope}| key {k} not in refs. did you mean {close}")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def refset_get(refs, *args, **kw):
|
|
50
|
+
out = {}
|
|
51
|
+
scope = kw.get("scope", "ref")
|
|
52
|
+
for k in refs:
|
|
53
|
+
try:
|
|
54
|
+
# print(k,refs[k])
|
|
55
|
+
out[k] = refs[k].value(refs[k].comp, **kw)
|
|
56
|
+
except Exception as e:
|
|
57
|
+
rf = refs[k]
|
|
58
|
+
log.error(e, f"{scope}| issue with ref: {rf}|{rf.key}|{rf.comp}")
|
|
59
|
+
|
|
60
|
+
return out
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# def f_root(ResRef: collections.OrderedDict, norm: dict = None):
|
|
64
|
+
# residual = [v.value() / (norm[k] if norm else 1) for k, v in ResRef.items()]
|
|
65
|
+
# res = np.array(residual)
|
|
66
|
+
# return res
|
|
67
|
+
#
|
|
68
|
+
#
|
|
69
|
+
# def f_min(ResRef: collections.OrderedDict, norm: dict = None):
|
|
70
|
+
# res = [v.value() / (norm[k] if norm else 1) for k, v in ResRef.items()]
|
|
71
|
+
# ans = np.linalg.norm(res, 2)
|
|
72
|
+
#
|
|
73
|
+
# if ans < 1:
|
|
74
|
+
# return ans**0.5
|
|
75
|
+
# return ans
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def eval_ref(canidate, *args, **kw):
|
|
79
|
+
# print(f'eval_ref {canidate,args,kw}')
|
|
80
|
+
if isinstance(canidate, Ref):
|
|
81
|
+
return canidate.value(*args, **kw)
|
|
82
|
+
elif callable(canidate):
|
|
83
|
+
o = canidate(*args, **kw)
|
|
84
|
+
return o
|
|
85
|
+
return canidate
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def scale_val(val, mult=None, bias=None, power=None) -> float:
|
|
89
|
+
if any((mult, bias, power)):
|
|
90
|
+
if power is not None:
|
|
91
|
+
val = val**power
|
|
92
|
+
if mult is not None:
|
|
93
|
+
val = mult * val
|
|
94
|
+
if bias is not None:
|
|
95
|
+
val = bias + val
|
|
96
|
+
return val
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def maybe_ref(can, astype=None, mult=None, bias=None, power=None, *args, **kw):
|
|
100
|
+
"""returns the value of a ref if it is a ref, otherwise returns the value"""
|
|
101
|
+
# print(f'maybe {can,astype}')
|
|
102
|
+
# print(can,astype,mult,bias,power,args,kw)
|
|
103
|
+
if isinstance(can, Ref):
|
|
104
|
+
val = can.value(*args, **kw)
|
|
105
|
+
return scale_val(val, mult, bias, power)
|
|
106
|
+
elif astype and isinstance(can, astype):
|
|
107
|
+
ref = can.as_ref_dict() # TODO: optimize this
|
|
108
|
+
return scale_val(ref.value(*args, **kw), mult, bias, power)
|
|
109
|
+
return can
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def maybe_attr_inst(can, astype=None):
|
|
113
|
+
"""returns the ref if is one otherwise convert it, otherwise returns the value"""
|
|
114
|
+
if isinstance(can, Ref):
|
|
115
|
+
return can
|
|
116
|
+
elif astype and isinstance(can, astype):
|
|
117
|
+
return astype.backref.handle_instance(can)
|
|
118
|
+
return can
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# Important State Preservation
|
|
122
|
+
# TODO: check for hidden X dependents / circular references ect.
|
|
123
|
+
# TODO: make global storage for Ref's based on the comp,key pair. This
|
|
124
|
+
class Ref:
|
|
125
|
+
"""A way to create portable references to system's and their component's properties, ref can also take a key to a zero argument function which will be evald. This is useful for creating an adhoc optimization problems dynamically between variables. However use must be carefully controlled to avoid circular references, and understanding the safe use of the refset_input and refset_get methods (changing state on actual system).
|
|
126
|
+
|
|
127
|
+
A dictionary can be used as a component, and the key will be used to access the value in the dictionary.
|
|
128
|
+
|
|
129
|
+
The key can be a callable of (*args,**kw) only in which case Ref.value(*args,**kw) will take input, and the ref will be evaluated as the result of the callable (allow_set=False), this renders the component unused, and disables the ability to set the value of the reference.
|
|
130
|
+
|
|
131
|
+
The ability to set this ref with another on key input allows for creating a reference that is identical to another reference except for the component provided if it is not None.
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
__slots__ = [
|
|
135
|
+
"comp",
|
|
136
|
+
"key",
|
|
137
|
+
"use_call",
|
|
138
|
+
"use_dict",
|
|
139
|
+
"allow_set",
|
|
140
|
+
"eval_f",
|
|
141
|
+
"key_override",
|
|
142
|
+
"_value_eval",
|
|
143
|
+
"_log_func",
|
|
144
|
+
"hxd",
|
|
145
|
+
"_name",
|
|
146
|
+
"attr_type",
|
|
147
|
+
]
|
|
148
|
+
comp: "TabulationMixin"
|
|
149
|
+
key: str
|
|
150
|
+
use_call: bool
|
|
151
|
+
use_dict: bool
|
|
152
|
+
allow_set: bool
|
|
153
|
+
eval_f: callable
|
|
154
|
+
key_override: bool
|
|
155
|
+
_value_eval: callable
|
|
156
|
+
_log_func: callable
|
|
157
|
+
attr_type: type
|
|
158
|
+
|
|
159
|
+
def __init__(self, comp, key, use_call=True, allow_set=True, eval_f=None):
|
|
160
|
+
self.set(comp, key, use_call, allow_set, eval_f)
|
|
161
|
+
|
|
162
|
+
def set(self, comp, key, use_call=True, allow_set=True, eval_f=None):
|
|
163
|
+
# key can be a ref, in which case this ref will be identical to the other ref except for the component provided if it is not None
|
|
164
|
+
from engforge.engforge_attributes import AttributedBaseMixin
|
|
165
|
+
|
|
166
|
+
if isinstance(key, Ref):
|
|
167
|
+
self.__dict__.update(key.__dict__)
|
|
168
|
+
if comp is not None:
|
|
169
|
+
self.comp = comp
|
|
170
|
+
return # a monkey patch
|
|
171
|
+
|
|
172
|
+
self.comp = comp
|
|
173
|
+
if isinstance(self.comp, dict):
|
|
174
|
+
self.use_dict = True
|
|
175
|
+
self._name = "dict"
|
|
176
|
+
else:
|
|
177
|
+
self.use_dict = False
|
|
178
|
+
|
|
179
|
+
self.key_override = False
|
|
180
|
+
if callable(key):
|
|
181
|
+
self.key_override = True
|
|
182
|
+
self.key = key # this should take have signature f(system,slv_info)
|
|
183
|
+
self.use_call = False
|
|
184
|
+
self.allow_set = False
|
|
185
|
+
self.eval_f = eval_f
|
|
186
|
+
self._name = "callable"
|
|
187
|
+
else:
|
|
188
|
+
self.key = key
|
|
189
|
+
self.use_call = use_call
|
|
190
|
+
self.allow_set = allow_set
|
|
191
|
+
self.eval_f = eval_f
|
|
192
|
+
if not self.use_dict:
|
|
193
|
+
self._name = self.comp.classname
|
|
194
|
+
|
|
195
|
+
self.attr_type = None
|
|
196
|
+
if self.allow_set and isinstance(self.comp, AttributedBaseMixin):
|
|
197
|
+
fieldz = self.comp.__class__.cls_all_attrs_fields()
|
|
198
|
+
atr_canidate = fieldz.get(self.key, None)
|
|
199
|
+
if isinstance(atr_canidate, attrs.Attribute):
|
|
200
|
+
self.attr_type = atr_canidate.type
|
|
201
|
+
|
|
202
|
+
if not hasattr(self, "_name"):
|
|
203
|
+
self._name = "NULL"
|
|
204
|
+
|
|
205
|
+
self.hxd = str(hex(id(self)))[-6:]
|
|
206
|
+
|
|
207
|
+
# TODO: update with change (pyee?)
|
|
208
|
+
self.setup_calls()
|
|
209
|
+
|
|
210
|
+
def setup_calls(self):
|
|
211
|
+
"""caches anonymous functions for value and logging speedup"""
|
|
212
|
+
if (
|
|
213
|
+
self.comp
|
|
214
|
+
and isinstance(self.comp, LoggingMixin)
|
|
215
|
+
and self.comp.log_level <= 2
|
|
216
|
+
):
|
|
217
|
+
self._log_func = lambda val: self.comp.msg(
|
|
218
|
+
f"REF[get] {str(self):<50} -> {val}"
|
|
219
|
+
)
|
|
220
|
+
else:
|
|
221
|
+
self._log_func = None
|
|
222
|
+
|
|
223
|
+
if self.key_override:
|
|
224
|
+
self._value_eval = lambda *a, **kw: self.key(*a, **kw)
|
|
225
|
+
else:
|
|
226
|
+
# do not cross reference vars!
|
|
227
|
+
# TODO: allo for comp/key changes with events
|
|
228
|
+
if self.key == "": # return the component
|
|
229
|
+
p = lambda *a, **kw: self.comp
|
|
230
|
+
elif self.use_dict:
|
|
231
|
+
p = lambda *a, **kw: self.comp.get(self.key)
|
|
232
|
+
elif self.key in self.comp.__dict__:
|
|
233
|
+
p = lambda *a, **kw: self.comp.__dict__[self.key]
|
|
234
|
+
else:
|
|
235
|
+
p = lambda *a, **kw: getattr(self.comp, self.key)
|
|
236
|
+
|
|
237
|
+
if self.eval_f:
|
|
238
|
+
g = lambda *a, **kw: self.eval_f(p(*a, **kw))
|
|
239
|
+
else:
|
|
240
|
+
g = p
|
|
241
|
+
|
|
242
|
+
self._value_eval = g
|
|
243
|
+
|
|
244
|
+
def copy(self, **changes):
|
|
245
|
+
"""copy the reference, and make changes to the copy"""
|
|
246
|
+
if "key" not in changes:
|
|
247
|
+
changes["key"] = self.key
|
|
248
|
+
if "comp" not in changes:
|
|
249
|
+
changes["comp"] = self.comp
|
|
250
|
+
cy = copy.copy(self)
|
|
251
|
+
cy.set(**changes)
|
|
252
|
+
return cy
|
|
253
|
+
|
|
254
|
+
def value(self, *args, **kw):
|
|
255
|
+
if log.log_level <= 10:
|
|
256
|
+
try:
|
|
257
|
+
o = self._value_eval(*args, **kw)
|
|
258
|
+
if self._log_func:
|
|
259
|
+
self._log_func(o)
|
|
260
|
+
return o
|
|
261
|
+
except Exception as e:
|
|
262
|
+
log.error(e, f"issue with ref: {self}|{self.key}|{self.comp}")
|
|
263
|
+
else:
|
|
264
|
+
o = self._value_eval(*args, **kw)
|
|
265
|
+
if self._log_func:
|
|
266
|
+
self._log_func(o)
|
|
267
|
+
return o
|
|
268
|
+
|
|
269
|
+
def set_value(self, val):
|
|
270
|
+
if self.allow_set:
|
|
271
|
+
if self.attr_type not in (str, float, int, bool):
|
|
272
|
+
if self.comp and self.comp.log_level < 10:
|
|
273
|
+
self.comp.msg(f"REF[set] {self} <- {val}")
|
|
274
|
+
return setattr(self.comp, self.key, val)
|
|
275
|
+
elif self.value() != val: # this increases perf. by reducing writes
|
|
276
|
+
if self.comp and self.comp.log_level < 10:
|
|
277
|
+
self.comp.msg(f"REF[set] {self} <- {val}")
|
|
278
|
+
return setattr(self.comp, self.key, val)
|
|
279
|
+
else:
|
|
280
|
+
raise Exception(f"not allowed to set value on {self.key}")
|
|
281
|
+
|
|
282
|
+
@property
|
|
283
|
+
def full_key(self):
|
|
284
|
+
if self.key_override:
|
|
285
|
+
return f"{self._name}.{self.key.__name__}"
|
|
286
|
+
else:
|
|
287
|
+
return f"{self._name}.{self.key}"
|
|
288
|
+
|
|
289
|
+
def __str__(self) -> str:
|
|
290
|
+
if self.use_dict:
|
|
291
|
+
return f"REF[{self.hxd}][DICT.{self.key}]"
|
|
292
|
+
return f"REF[{self.hxd}][{self.full_key}]"
|
|
293
|
+
|
|
294
|
+
def __repr__(self) -> str:
|
|
295
|
+
return f"REF[{self.hxd}][{self.full_key}]"
|
|
296
|
+
|
|
297
|
+
# Utilty Methods
|
|
298
|
+
refset_get = refset_get
|
|
299
|
+
refset_input = refset_input
|