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
|
@@ -0,0 +1,935 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from engforge.datastores.datastores.data import *
|
|
4
|
+
from engforge.tabulation import *
|
|
5
|
+
from engforge.configuration import *
|
|
6
|
+
from engforge.components import *
|
|
7
|
+
|
|
8
|
+
# from engforge.patterns import SingletonMeta
|
|
9
|
+
|
|
10
|
+
from engforge.analysis import *
|
|
11
|
+
|
|
12
|
+
import attr
|
|
13
|
+
import random
|
|
14
|
+
|
|
15
|
+
from sqlalchemy import ForeignKey, Column, Table, MetaData, Unicode, UnicodeText
|
|
16
|
+
from sqlalchemy.orm import relationship
|
|
17
|
+
from sqlalchemy.orm.collections import attribute_mapped_collection
|
|
18
|
+
from sqlalchemy.ext.associationproxy import association_proxy
|
|
19
|
+
from sqlalchemy.ext.declarative import declared_attr
|
|
20
|
+
from sqlalchemy.sql.sqltypes import (
|
|
21
|
+
BOOLEAN,
|
|
22
|
+
NUMERIC,
|
|
23
|
+
VARCHAR,
|
|
24
|
+
INTEGER,
|
|
25
|
+
INT,
|
|
26
|
+
Integer,
|
|
27
|
+
String,
|
|
28
|
+
Boolean,
|
|
29
|
+
Numeric,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
from sqlalchemy.sql import func
|
|
33
|
+
|
|
34
|
+
from threading import Thread
|
|
35
|
+
|
|
36
|
+
from sqlalchemy.sql.expression import Insert
|
|
37
|
+
from sqlalchemy.dialects.postgresql import insert
|
|
38
|
+
from sqlalchemy.ext.compiler import compiles
|
|
39
|
+
|
|
40
|
+
# TODO: add schema info
|
|
41
|
+
ReportBase = declarative_base()
|
|
42
|
+
|
|
43
|
+
DEFAULT_STRING_LENGTH = 256
|
|
44
|
+
|
|
45
|
+
"""A Module in which we reflect changing schema in components or other tabulationmixin instances when solved by an an analysis.
|
|
46
|
+
|
|
47
|
+
To use, add `ReporingMixin` to any engforge `Analysis` class
|
|
48
|
+
|
|
49
|
+
There are two major components to this module, that follow from the working plan:
|
|
50
|
+
1) Investigate the existing database and create the tables nessicary
|
|
51
|
+
2) upload the data to the database in the respective tables
|
|
52
|
+
3) If nessicary update or create a view for each component and analysis (hard part!)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
At the time of creation attributes with engforge validators will be created as columns in the component table, where as only numeric key value pairs will be added to the components Vertical Attribute Mapping table (VAM)
|
|
56
|
+
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
log = logging.getLogger("engforge-report")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# ignore duplicates
|
|
63
|
+
@compiles(Insert, "postgresql")
|
|
64
|
+
def ignore_duplicates(insert, compiler, **kw):
|
|
65
|
+
s = compiler.visit_insert(insert, **kw)
|
|
66
|
+
ignore = insert.kwargs.get("postgresql_ignore_duplicates", False)
|
|
67
|
+
return s if not ignore else s + " ON CONFLICT DO NOTHING"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
Insert.argument_for("postgresql", "ignore_duplicates", None)
|
|
71
|
+
|
|
72
|
+
# TODO: make new sqalchemy base, dont mixin other bases into our special fancy db
|
|
73
|
+
|
|
74
|
+
# These Mixins & Base Table Classes Help Us By Defining Common Bases For tables
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class MappedDictMixin:
|
|
78
|
+
"""Adds obj[key] access to a mapped class.
|
|
79
|
+
|
|
80
|
+
This class basically proxies dictionary access to an attribute
|
|
81
|
+
called ``_proxied``. The class which inherits this class
|
|
82
|
+
should have an attribute called ``_proxied`` which points to a dictionary.
|
|
83
|
+
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
def __len__(self):
|
|
87
|
+
return len(self._proxied)
|
|
88
|
+
|
|
89
|
+
def __iter__(self):
|
|
90
|
+
return iter(self._proxied)
|
|
91
|
+
|
|
92
|
+
def __getitem__(self, key):
|
|
93
|
+
return self._proxied[key]
|
|
94
|
+
|
|
95
|
+
def __contains__(self, key):
|
|
96
|
+
return key in self._proxied
|
|
97
|
+
|
|
98
|
+
def __setitem__(self, key, value):
|
|
99
|
+
self._proxied[key] = value
|
|
100
|
+
|
|
101
|
+
def __delitem__(self, key):
|
|
102
|
+
del self._proxied[key]
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# TODO: Bases for AttrTable, CompTable, AnalysisTable
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class AttrBase(ReportBase):
|
|
109
|
+
__abstract__ = True
|
|
110
|
+
# id = Column(Integer(), primary_key=True)
|
|
111
|
+
key = Column(Unicode(64), nullable=False, primary_key=True)
|
|
112
|
+
value = Column(Numeric)
|
|
113
|
+
|
|
114
|
+
def __init__(self, result_id_in, key=None, value=None):
|
|
115
|
+
# log.debug(f'creating attr rid:{result_id_in} {self}, {key}={value}')
|
|
116
|
+
|
|
117
|
+
self.result_id = result_id_in
|
|
118
|
+
|
|
119
|
+
if key is not None and value is not None:
|
|
120
|
+
if isinstance(value, (float, int)):
|
|
121
|
+
self.key = key
|
|
122
|
+
self.value = float(value)
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def unique_keys(self):
|
|
126
|
+
rr = ResultsRegistry()
|
|
127
|
+
with rr.db.session_scope() as sesh:
|
|
128
|
+
results = list(set(sesh.query(self.__class__.key).all()))
|
|
129
|
+
return results
|
|
130
|
+
|
|
131
|
+
@classmethod
|
|
132
|
+
def items(cls):
|
|
133
|
+
rr = ResultsRegistry()
|
|
134
|
+
with rr.db.session_scope() as sesh:
|
|
135
|
+
results = list(sesh.query(cls).all())
|
|
136
|
+
return results
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class TableBase(MappedDictMixin, ReportBase):
|
|
140
|
+
__abstract__ = True
|
|
141
|
+
|
|
142
|
+
check_types = {
|
|
143
|
+
String: (str,),
|
|
144
|
+
Integer: (int,),
|
|
145
|
+
Numeric: (float, int),
|
|
146
|
+
Boolean: (bool,),
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
store_type = {String: str, Integer: int, Numeric: float, Boolean: bool}
|
|
150
|
+
|
|
151
|
+
ignore_keys = ("id", "index")
|
|
152
|
+
|
|
153
|
+
attr_class = None
|
|
154
|
+
|
|
155
|
+
attr_store = None
|
|
156
|
+
|
|
157
|
+
def __init__(self, result_id_in, **kwargs):
|
|
158
|
+
self.attr_store = {}
|
|
159
|
+
if "result_id_in" in self.__dict__:
|
|
160
|
+
self.result_id_in = result_id_in
|
|
161
|
+
|
|
162
|
+
log.debug(f"creating TB id:{result_id_in} obj:{self}")
|
|
163
|
+
table = self.__class__.__table__
|
|
164
|
+
cols = self.dbi_columns
|
|
165
|
+
|
|
166
|
+
for key, val in kwargs.items():
|
|
167
|
+
key = key.lower()
|
|
168
|
+
vtype = type(val)
|
|
169
|
+
|
|
170
|
+
if key not in self.ignore_keys: # we will try to store the data
|
|
171
|
+
if key in cols: # we will map the data to the column
|
|
172
|
+
col = table.columns[key]
|
|
173
|
+
ctype = type(col.type)
|
|
174
|
+
|
|
175
|
+
# log.debug(f'{self} setting {key}|{col}|{ctype}|{vtype}')
|
|
176
|
+
|
|
177
|
+
in_stypes = any(
|
|
178
|
+
[
|
|
179
|
+
issubclass(ctype, check_type)
|
|
180
|
+
for check_type in self.check_types
|
|
181
|
+
]
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
if in_stypes:
|
|
185
|
+
use_ctype = ctype
|
|
186
|
+
if not ctype in self.check_types:
|
|
187
|
+
valids = list(
|
|
188
|
+
filter(
|
|
189
|
+
lambda ct: issubclass(ctype, ct),
|
|
190
|
+
list(self.check_types.keys()),
|
|
191
|
+
)
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
if valids:
|
|
195
|
+
if len(valids) > 1:
|
|
196
|
+
self.warning(
|
|
197
|
+
"more than one type matched for!!! {ctype}->{valids} "
|
|
198
|
+
)
|
|
199
|
+
use_ctype = valids[0]
|
|
200
|
+
# log.debug(f'getting registered type {use_ctype} for {ctype}')
|
|
201
|
+
|
|
202
|
+
in_type = isinstance(val, self.check_types[use_ctype])
|
|
203
|
+
|
|
204
|
+
else:
|
|
205
|
+
in_type = False
|
|
206
|
+
|
|
207
|
+
if in_stypes and in_type:
|
|
208
|
+
setattr(self, key, self.store_type[use_ctype](val))
|
|
209
|
+
else:
|
|
210
|
+
log.debug(
|
|
211
|
+
f"{key}={val} {vtype}=>{use_ctype}? col-valid:{in_stypes} valid-type:{in_type} col:{col}"
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
elif isinstance(val, (float, int)): # dynamic mapping
|
|
215
|
+
if not numpy.isnan(val):
|
|
216
|
+
atobj = self.attr_class(result_id_in, key=key, value=val)
|
|
217
|
+
self.attr_store[key] = atobj
|
|
218
|
+
# self[key] = atobj #dont use orm for upload!
|
|
219
|
+
|
|
220
|
+
# else:
|
|
221
|
+
# log.debug(f'{key} not found for columns {cols}')
|
|
222
|
+
|
|
223
|
+
# these are breaking the analysis table creation since we can't make the attr_class before initiation when @declared_attr is made
|
|
224
|
+
# @declared_attr
|
|
225
|
+
# def dict_values(cls):
|
|
226
|
+
# if cls.attr_class:
|
|
227
|
+
# return relationship( cls.attr_class.__name__, collection_class = attribute_mapped_collection("key") )
|
|
228
|
+
|
|
229
|
+
# @declared_attr
|
|
230
|
+
# def _proxied(cls):
|
|
231
|
+
# return association_proxy( "dict_values", "value")
|
|
232
|
+
|
|
233
|
+
@property
|
|
234
|
+
def dbi_columns(self):
|
|
235
|
+
return list(self.__class__.__table__.columns.keys())
|
|
236
|
+
|
|
237
|
+
@classmethod
|
|
238
|
+
def db_columns(cls):
|
|
239
|
+
if "__table__" in cls.__dict__ and cls.__table__ is not None:
|
|
240
|
+
return set(list(cls.__table__.c.keys()))
|
|
241
|
+
|
|
242
|
+
@classmethod
|
|
243
|
+
def all_fields(cls):
|
|
244
|
+
if cls._mapped_component is None:
|
|
245
|
+
return []
|
|
246
|
+
all_table_fields = set(
|
|
247
|
+
[attr.lower() for attr in cls._mapped_component.cls_all_property_labels()]
|
|
248
|
+
)
|
|
249
|
+
all_attr_fields = set(
|
|
250
|
+
[
|
|
251
|
+
attr.lower()
|
|
252
|
+
for attr in cls._mapped_component.cls_all_attrs_fields().keys()
|
|
253
|
+
]
|
|
254
|
+
)
|
|
255
|
+
all_fields = set.union(all_table_fields, all_attr_fields)
|
|
256
|
+
return set(list(all_fields))
|
|
257
|
+
|
|
258
|
+
@classmethod
|
|
259
|
+
def dynamic_columns(cls):
|
|
260
|
+
if cls._mapped_component is None:
|
|
261
|
+
return []
|
|
262
|
+
|
|
263
|
+
return set.difference(cls.all_fields(), cls.db_columns())
|
|
264
|
+
|
|
265
|
+
@classmethod
|
|
266
|
+
def items(cls):
|
|
267
|
+
rr = ResultsRegistry()
|
|
268
|
+
with rr.db.session_scope() as sesh:
|
|
269
|
+
results = list(sesh.query(cls).all())
|
|
270
|
+
return results
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
class ReportingMixin:
|
|
274
|
+
"""Add to an analysis component"""
|
|
275
|
+
|
|
276
|
+
_report_db = None
|
|
277
|
+
|
|
278
|
+
@property
|
|
279
|
+
def report_db(self):
|
|
280
|
+
if self._report_db is not None:
|
|
281
|
+
return self._report_db
|
|
282
|
+
else:
|
|
283
|
+
self.warning("no report db connection set!")
|
|
284
|
+
|
|
285
|
+
# else: #this is alot, lets be more careful
|
|
286
|
+
# self.warning('autogenerating database...')
|
|
287
|
+
# report_db = DBConnection(database_name='reports')
|
|
288
|
+
# report_db.ensure_database_exists()
|
|
289
|
+
|
|
290
|
+
@report_db.setter
|
|
291
|
+
def report_db(self, inputval: DBConnection):
|
|
292
|
+
if isinstance(inputval, DBConnection):
|
|
293
|
+
self._report_db = inputval
|
|
294
|
+
else:
|
|
295
|
+
self.warning(f"Got bad value {inputval} of type {type(inputval)}")
|
|
296
|
+
|
|
297
|
+
def report_data(self, use_thread=False):
|
|
298
|
+
if self.report_db and self.solved:
|
|
299
|
+
try:
|
|
300
|
+
assert isinstance(self, Analysis)
|
|
301
|
+
rr = ResultsRegistry(self.report_db)
|
|
302
|
+
rr.ensure_analysis(self)
|
|
303
|
+
return rr.upload_analysis(self, use_thread=use_thread)
|
|
304
|
+
|
|
305
|
+
except Exception as e:
|
|
306
|
+
self.error(e, "Issue Reporting Data")
|
|
307
|
+
|
|
308
|
+
elif not self.solved:
|
|
309
|
+
self.warning("Analysis Not Solved, Cannot Upload")
|
|
310
|
+
|
|
311
|
+
elif not self.report_db:
|
|
312
|
+
self.warning("No Report Database Initated")
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
@forge
|
|
316
|
+
class ResultsRegistry(Configuration, metaclass=SingletonMeta):
|
|
317
|
+
"""An instance to manage the various database mappings to this live code repository"""
|
|
318
|
+
|
|
319
|
+
default_args = {"keep_existing": True, "autoload": False}
|
|
320
|
+
|
|
321
|
+
db = attr.ib()
|
|
322
|
+
base = attr.ib(
|
|
323
|
+
default=ReportBase
|
|
324
|
+
) # ,factory=declarative_base) #slot for sqlalchemy base
|
|
325
|
+
|
|
326
|
+
_component_cls_table_mapping = None
|
|
327
|
+
|
|
328
|
+
schema = "report"
|
|
329
|
+
|
|
330
|
+
analysis_table_abrv = ""
|
|
331
|
+
analysis_attr_abrv = "anly_attr_"
|
|
332
|
+
component_table_abrv = "comp_tbl_"
|
|
333
|
+
component_attr_abrv = "comp_attr_"
|
|
334
|
+
|
|
335
|
+
def __on_init__(self):
|
|
336
|
+
self.info("creating registry and ensuring db exists!")
|
|
337
|
+
self.db.ensure_database_exists(create_meta=False)
|
|
338
|
+
|
|
339
|
+
self.base.metadata.bind = self.db.engine
|
|
340
|
+
self.base.metadata.reflect(self.db.engine, autoload=True, keep_existing=True)
|
|
341
|
+
|
|
342
|
+
self._component_cls_table_mapping = {}
|
|
343
|
+
self.initalize()
|
|
344
|
+
|
|
345
|
+
def initalize(self):
|
|
346
|
+
AnalysisRegistry.__table__.create(self.db.engine, checkfirst=True)
|
|
347
|
+
ComponentRegistry.__table__.create(self.db.engine, checkfirst=True)
|
|
348
|
+
|
|
349
|
+
@property
|
|
350
|
+
def db_classes(self):
|
|
351
|
+
return list(set([val[0] for val in self.mapped_tables.values()]))
|
|
352
|
+
|
|
353
|
+
@property
|
|
354
|
+
def component_classes(self):
|
|
355
|
+
return list(set([val[1] for val in self.mapped_tables.values()]))
|
|
356
|
+
|
|
357
|
+
@property
|
|
358
|
+
def mapped_classes(self):
|
|
359
|
+
"""schema class : db class"""
|
|
360
|
+
return {val[1]: val[0] for val in self.mapped_tables.values()}
|
|
361
|
+
|
|
362
|
+
@property
|
|
363
|
+
def mapped_tables(self):
|
|
364
|
+
return self._component_cls_table_mapping
|
|
365
|
+
|
|
366
|
+
@property
|
|
367
|
+
def analyses(self):
|
|
368
|
+
return Analysis.component_subclasses()
|
|
369
|
+
|
|
370
|
+
@property
|
|
371
|
+
def components(self):
|
|
372
|
+
comps = Component.component_subclasses()
|
|
373
|
+
comp_subclasses = set(comps)
|
|
374
|
+
analy_subclasses = set(Analysis.component_subclasses())
|
|
375
|
+
comp_keys = set.difference(comp_subclasses, analy_subclasses)
|
|
376
|
+
|
|
377
|
+
return {ckey: comps[ckey] for ckey in comp_keys}
|
|
378
|
+
|
|
379
|
+
def filter_by_validators(self, attr_obj):
|
|
380
|
+
if not attr_obj.validator:
|
|
381
|
+
return False
|
|
382
|
+
|
|
383
|
+
if not isinstance(attr_obj.validator, TAB_VALIDATOR_TYPE):
|
|
384
|
+
return False
|
|
385
|
+
|
|
386
|
+
valtype = type(attr_obj.validator.type)
|
|
387
|
+
if not valtype:
|
|
388
|
+
return False
|
|
389
|
+
if valtype is tuple and any(
|
|
390
|
+
[gtype in attr_obj.validator.type for gtype in (str, float, int)]
|
|
391
|
+
):
|
|
392
|
+
return True
|
|
393
|
+
if any([gtype is attr_obj.validator.type for gtype in (str, float, int)]):
|
|
394
|
+
return True
|
|
395
|
+
return False
|
|
396
|
+
|
|
397
|
+
def validator_to_column(self, attr_obj):
|
|
398
|
+
if type(attr_obj.validator.type) is tuple and any(
|
|
399
|
+
[gtype in attr_obj.validator.type for gtype in (float, int)]
|
|
400
|
+
):
|
|
401
|
+
return Column(Numeric, default=attr_obj.default, nullable=True)
|
|
402
|
+
if type(attr_obj.validator.type) is tuple and str in attr_obj.validator.type:
|
|
403
|
+
return Column(
|
|
404
|
+
String(DEFAULT_STRING_LENGTH),
|
|
405
|
+
default=attr_obj.default,
|
|
406
|
+
nullable=True,
|
|
407
|
+
)
|
|
408
|
+
if str is attr_obj.validator.type:
|
|
409
|
+
return Column(
|
|
410
|
+
String(DEFAULT_STRING_LENGTH),
|
|
411
|
+
default=attr_obj.default,
|
|
412
|
+
nullable=True,
|
|
413
|
+
)
|
|
414
|
+
return Column(Numeric, default=attr_obj.default, nullable=True)
|
|
415
|
+
|
|
416
|
+
def dict_attr(
|
|
417
|
+
self, comp_cls, target_table_id, results_table=None, is_analysis=False
|
|
418
|
+
):
|
|
419
|
+
"""we make the default entries for a components attribues table"""
|
|
420
|
+
|
|
421
|
+
# this modulelates names for componets / analysis tables
|
|
422
|
+
table_abrv = self.component_attr_abrv
|
|
423
|
+
if issubclass(comp_cls, Analysis):
|
|
424
|
+
table_abrv = self.analysis_attr_abrv
|
|
425
|
+
name = comp_cls.__name__.lower()
|
|
426
|
+
|
|
427
|
+
default_attr = {
|
|
428
|
+
"__tablename__": f"{table_abrv}{name}",
|
|
429
|
+
#'comp_id': Column(Integer,ForeignKey( target_table_id )),
|
|
430
|
+
"__table_args__": self.default_args,
|
|
431
|
+
"__abstract__": False,
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if results_table is not None and not is_analysis: # analysis won't be mapped
|
|
435
|
+
default_attr["__tablename__"] = f"{results_table}_{table_abrv}{name}"
|
|
436
|
+
root_obj, _ = self.mapped_tables[results_table] # analysis won't be mapped
|
|
437
|
+
backref_name = f"db_comp_attr_{name}"
|
|
438
|
+
default_attr["result_id"] = Column(
|
|
439
|
+
Integer, ForeignKey(f"{results_table}.id"), primary_key=True
|
|
440
|
+
)
|
|
441
|
+
default_attr["analysis"] = relationship(
|
|
442
|
+
root_obj.__name__, backref=backref(backref_name, lazy="noload")
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
elif issubclass(comp_cls, Analysis) and is_analysis:
|
|
446
|
+
default_attr["__tablename__"] = f"{table_abrv}{name}"
|
|
447
|
+
backref_name = f"db_comp_attr_{name}"
|
|
448
|
+
default_attr["result_id"] = Column(
|
|
449
|
+
Integer, ForeignKey(target_table_id), primary_key=True
|
|
450
|
+
)
|
|
451
|
+
# default_attr['analysis'] = relationship(comp_cls.__name__,backref = backref(backref_name,lazy='noload'))
|
|
452
|
+
|
|
453
|
+
return default_attr
|
|
454
|
+
|
|
455
|
+
def default_attr(self, comp_cls, results_table=None):
|
|
456
|
+
"""we make the default entries for a components primary table"""
|
|
457
|
+
|
|
458
|
+
table_abrv = self.component_table_abrv
|
|
459
|
+
if issubclass(comp_cls, Analysis):
|
|
460
|
+
table_abrv = self.analysis_table_abrv
|
|
461
|
+
|
|
462
|
+
name = comp_cls.__name__.lower()
|
|
463
|
+
# TODO: Add mapping __init__ method
|
|
464
|
+
default_attr = {
|
|
465
|
+
"__tablename__": f"{table_abrv}{name}",
|
|
466
|
+
#'id': Column(Integer, primary_key=True),
|
|
467
|
+
"__table_args__": self.default_args,
|
|
468
|
+
"__abstract__": False,
|
|
469
|
+
"_mapped_component": comp_cls,
|
|
470
|
+
"attr_store": None,
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if results_table is not None:
|
|
474
|
+
tbl_name = f"{results_table}_{table_abrv}{name}"
|
|
475
|
+
backref_name = f"db_comp_{name}"
|
|
476
|
+
root_obj, _ = self.mapped_tables[results_table]
|
|
477
|
+
default_attr["__tablename__"] = f"{results_table}_{table_abrv}{name}"
|
|
478
|
+
default_attr["result_id"] = Column(
|
|
479
|
+
Integer, ForeignKey(f"{results_table}.id"), primary_key=True
|
|
480
|
+
)
|
|
481
|
+
default_attr["analysis"] = relationship(
|
|
482
|
+
root_obj.__name__, backref=backref(backref_name, lazy="noload")
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
else: # its an analysis
|
|
486
|
+
default_attr["created"] = Column(DateTime, server_default=func.now())
|
|
487
|
+
default_attr["active"] = Column(Boolean(), server_default="t")
|
|
488
|
+
default_attr["run_id"] = Column(
|
|
489
|
+
String(36)
|
|
490
|
+
) # corresponds to uuid set in analysis
|
|
491
|
+
default_attr["id"]: Column(Integer, primary_key=True)
|
|
492
|
+
# default_attr['components'] = relationship(TableBase, back_populates= "analysis")
|
|
493
|
+
|
|
494
|
+
return default_attr
|
|
495
|
+
|
|
496
|
+
# These use keys of system_property or attr and lower() so no spaces or capitals
|
|
497
|
+
def all_possible_component_fields(self, cls):
|
|
498
|
+
all_table_fields = set([attr.lower() for attr in cls.cls_all_property_keys()])
|
|
499
|
+
all_attr_fields = set(
|
|
500
|
+
[attr.lower() for attr in cls.cls_all_attrs_fields().keys()]
|
|
501
|
+
)
|
|
502
|
+
all_fields = set.union(all_table_fields, all_attr_fields)
|
|
503
|
+
return set(list(all_fields))
|
|
504
|
+
|
|
505
|
+
def component_attr(self, comp_cls, results_table=None):
|
|
506
|
+
# #TODO: assign key values as attribute dict
|
|
507
|
+
# #TODO: check if class has a __tablename__ and if it does use that with, ensure component_table_ on front
|
|
508
|
+
|
|
509
|
+
component_attr = {
|
|
510
|
+
key.lower(): self.validator_to_column(field)
|
|
511
|
+
for key, field in comp_cls.cls_all_attrs_fields().items()
|
|
512
|
+
if self.filter_by_validators(field)
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
component_attr.update(
|
|
516
|
+
{
|
|
517
|
+
k.lower(): Column(k.lower(), Numeric(), nullable=True)
|
|
518
|
+
for k in comp_cls.cls_all_property_keys()
|
|
519
|
+
}
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
component_attr.update(self.default_attr(comp_cls, results_table=results_table))
|
|
523
|
+
return component_attr
|
|
524
|
+
|
|
525
|
+
def db_component_dict(self, comp_cls, results_table=None, is_analysis=False):
|
|
526
|
+
"""returns the nessicary table types to make for reflection of input component class
|
|
527
|
+
|
|
528
|
+
this method represents the dynamic creation of SQLA typing and attributes via __dict__
|
|
529
|
+
"""
|
|
530
|
+
|
|
531
|
+
assert isinstance(comp_cls, type)
|
|
532
|
+
assert Component in comp_cls.mro()
|
|
533
|
+
|
|
534
|
+
comp_attr = self.component_attr(comp_cls, results_table=results_table)
|
|
535
|
+
tablename = comp_attr["__tablename__"]
|
|
536
|
+
|
|
537
|
+
if results_table:
|
|
538
|
+
comp_cls_name = f"DB_{results_table}_{comp_cls.__name__}"
|
|
539
|
+
|
|
540
|
+
else:
|
|
541
|
+
comp_cls_name = f"DB_{comp_cls.__name__}"
|
|
542
|
+
|
|
543
|
+
if is_analysis:
|
|
544
|
+
results_table = comp_attr["__tablename__"] # pass reference for id!
|
|
545
|
+
|
|
546
|
+
if is_analysis: # We create the analysis first
|
|
547
|
+
if "id" not in comp_attr:
|
|
548
|
+
comp_attr["id"] = Column(Integer, primary_key=True)
|
|
549
|
+
|
|
550
|
+
comp_db_tup = (comp_cls_name, (TableBase,), comp_attr)
|
|
551
|
+
comp_db = self.check_or_create_db_type(
|
|
552
|
+
comp_attr["__tablename__"], comp_db_tup
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
dict_attr = self.dict_attr(
|
|
556
|
+
comp_cls,
|
|
557
|
+
f"{comp_attr['__tablename__']}.id",
|
|
558
|
+
results_table=results_table,
|
|
559
|
+
is_analysis=is_analysis,
|
|
560
|
+
)
|
|
561
|
+
dict_name = f"{comp_cls_name}_Dict"
|
|
562
|
+
dict_db_tup = (dict_name, (AttrBase,), dict_attr)
|
|
563
|
+
dict_db = self.check_or_create_db_type(
|
|
564
|
+
dict_attr["__tablename__"], dict_db_tup
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
comp_db.attr_class = dict_db # This delayed monkey patch is fine since we wont be able to try any init's with this class until
|
|
568
|
+
|
|
569
|
+
else:
|
|
570
|
+
dict_attr = self.dict_attr(
|
|
571
|
+
comp_cls,
|
|
572
|
+
f"{comp_attr['__tablename__']}.id",
|
|
573
|
+
results_table=results_table,
|
|
574
|
+
)
|
|
575
|
+
dict_name = f"{comp_cls_name}_Dict"
|
|
576
|
+
dict_db_tup = (dict_name, (AttrBase,), dict_attr)
|
|
577
|
+
dict_db = self.check_or_create_db_type(
|
|
578
|
+
dict_attr["__tablename__"], dict_db_tup
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
comp_attr["attr_class"] = dict_db
|
|
582
|
+
comp_db_tup = (comp_cls_name, (TableBase,), comp_attr)
|
|
583
|
+
comp_db = self.check_or_create_db_type(
|
|
584
|
+
comp_attr["__tablename__"], comp_db_tup
|
|
585
|
+
)
|
|
586
|
+
|
|
587
|
+
return {
|
|
588
|
+
comp_attr["__tablename__"]: comp_db,
|
|
589
|
+
dict_attr["__tablename__"]: dict_db,
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
def check_or_create_db_type(self, tablename, type_tuple):
|
|
593
|
+
cls_name, ttype, attr_dict = type_tuple
|
|
594
|
+
|
|
595
|
+
if self.db.engine.has_table(tablename):
|
|
596
|
+
remove = set(["__table_args__"])
|
|
597
|
+
for key, item in attr_dict.items():
|
|
598
|
+
if issubclass(type(item), Column):
|
|
599
|
+
remove.add(key)
|
|
600
|
+
|
|
601
|
+
for rkey in remove:
|
|
602
|
+
if rkey in attr_dict:
|
|
603
|
+
# self.debug(f'removing {rkey}')
|
|
604
|
+
attr_dict.pop(rkey)
|
|
605
|
+
else:
|
|
606
|
+
self.debug(f"couldnt find {rkey}")
|
|
607
|
+
|
|
608
|
+
attr_dict["__table__"] = self.base.metadata.tables[tablename]
|
|
609
|
+
attr_dict["__table_args__"] = {"autoload": True}
|
|
610
|
+
|
|
611
|
+
self.debug(f"making type: {cls_name,self.base,attr_dict}")
|
|
612
|
+
cls_db = type(cls_name, ttype, attr_dict)
|
|
613
|
+
|
|
614
|
+
self.debug(
|
|
615
|
+
f"mapped existing table to type {tablename} -> {cls_db.__name__}"
|
|
616
|
+
)
|
|
617
|
+
setattr(self, cls_db.__name__, cls_db)
|
|
618
|
+
|
|
619
|
+
return cls_db
|
|
620
|
+
|
|
621
|
+
else:
|
|
622
|
+
cls_db = type(*type_tuple)
|
|
623
|
+
self.debug(
|
|
624
|
+
f"created table type {cls_db.__name__} -> {cls_db.__tablename__}"
|
|
625
|
+
)
|
|
626
|
+
|
|
627
|
+
setattr(self, cls_db.__name__, cls_db)
|
|
628
|
+
return cls_db
|
|
629
|
+
|
|
630
|
+
def map_component(self, dbcomponent, component):
|
|
631
|
+
self.debug(f"mapping {dbcomponent},{component} -> {dbcomponent.__tablename__}")
|
|
632
|
+
# add to internal mapping
|
|
633
|
+
self._component_cls_table_mapping[dbcomponent.__tablename__] = (
|
|
634
|
+
dbcomponent,
|
|
635
|
+
component,
|
|
636
|
+
)
|
|
637
|
+
|
|
638
|
+
tablenames = self.db.engine.table_names()
|
|
639
|
+
atables = AnalysisRegistry.tablenames(self.db)
|
|
640
|
+
ctables = ComponentRegistry.tablenames(self.db)
|
|
641
|
+
|
|
642
|
+
self.debug(f"comparing {tablenames} | {atables} | {ctables}")
|
|
643
|
+
# create record of components and analyses
|
|
644
|
+
if isinstance(component, Analysis) or issubclass(component, Analysis):
|
|
645
|
+
if isinstance(component, Analysis):
|
|
646
|
+
component = component.__class__
|
|
647
|
+
if dbcomponent.__tablename__ not in atables:
|
|
648
|
+
self.info(f"adding analysis record { dbcomponent.__tablename__}")
|
|
649
|
+
with self.db.session_scope() as sesh:
|
|
650
|
+
rec = AnalysisRegistry(dbcomponent, component)
|
|
651
|
+
sesh.add(rec)
|
|
652
|
+
|
|
653
|
+
elif isinstance(component, Component) or issubclass(component, Component):
|
|
654
|
+
if isinstance(component, Component):
|
|
655
|
+
component = component.__class__
|
|
656
|
+
if dbcomponent.__tablename__ not in ctables:
|
|
657
|
+
self.info(f"adding comonent record { dbcomponent.__tablename__}")
|
|
658
|
+
with self.db.session_scope() as sesh:
|
|
659
|
+
rec = ComponentRegistry(dbcomponent, component)
|
|
660
|
+
sesh.add(rec)
|
|
661
|
+
|
|
662
|
+
def ensure_all_tables(self):
|
|
663
|
+
"""creates a set of component tables linked to each analysis"""
|
|
664
|
+
|
|
665
|
+
for cls_name, cls in self.analyses.items():
|
|
666
|
+
self.debug(f"ensure-all-tables: {cls}")
|
|
667
|
+
self.ensure_analysis(cls)
|
|
668
|
+
|
|
669
|
+
def ensure_component_tables(self, results_table=None):
|
|
670
|
+
for cls_name, cls in self.components.items():
|
|
671
|
+
self.debug(f"ensure-comp-tables: {cls}")
|
|
672
|
+
self.ensure_component_table(cls, results_table=results_table)
|
|
673
|
+
|
|
674
|
+
def ensure_component_tables_in_analysis(self, analysis, results_table=None):
|
|
675
|
+
assert isinstance(analysis, Analysis)
|
|
676
|
+
for cls in analysis.unique_internal_components_classes:
|
|
677
|
+
self.debug(f"ensure-analyinst-tables: {cls}")
|
|
678
|
+
self.ensure_component_table(cls, results_table=results_table)
|
|
679
|
+
|
|
680
|
+
def ensure_component_table(self, component_cls, results_table=None):
|
|
681
|
+
self.debug(f"ensure-comp-table: {component_cls}")
|
|
682
|
+
|
|
683
|
+
is_analysis = False
|
|
684
|
+
if issubclass(component_cls, Analysis):
|
|
685
|
+
is_analysis = True
|
|
686
|
+
|
|
687
|
+
self.info(f"ensuring component {component_cls} analysis?:{is_analysis}")
|
|
688
|
+
db_component_type_dict = self.db_component_dict(
|
|
689
|
+
component_cls, results_table=results_table, is_analysis=is_analysis
|
|
690
|
+
)
|
|
691
|
+
for comp_tbl, compdb in db_component_type_dict.items():
|
|
692
|
+
if not self.db.engine.has_table(comp_tbl):
|
|
693
|
+
self.info(f"creating tables {comp_tbl}")
|
|
694
|
+
compdb.__table__.create(self.db.engine, checkfirst=True)
|
|
695
|
+
|
|
696
|
+
else:
|
|
697
|
+
self.debug(f"table exists already {comp_tbl}")
|
|
698
|
+
|
|
699
|
+
for comp_tbl, compdb in db_component_type_dict.items():
|
|
700
|
+
if not compdb.__name__.endswith("Dict"):
|
|
701
|
+
self.map_component(compdb, component_cls)
|
|
702
|
+
|
|
703
|
+
return db_component_type_dict
|
|
704
|
+
|
|
705
|
+
def ensure_analysis(self, analysis):
|
|
706
|
+
"""pass a class or instance to create the tables, passing an instance also ensures its internal components are
|
|
707
|
+
tabulated"""
|
|
708
|
+
self.info(f"ensure-analysis: {analysis}")
|
|
709
|
+
analysis_cls = self.get_analysis_class(analysis)
|
|
710
|
+
|
|
711
|
+
db_component_type_dict = self.ensure_component_table(analysis_cls)
|
|
712
|
+
|
|
713
|
+
# return primary table name
|
|
714
|
+
out = [
|
|
715
|
+
dbckey
|
|
716
|
+
for dbckey in db_component_type_dict.keys()
|
|
717
|
+
if dbckey.startswith(self.analysis_table_abrv)
|
|
718
|
+
]
|
|
719
|
+
if out:
|
|
720
|
+
analysis_table = out[0]
|
|
721
|
+
if analysis is analysis_cls:
|
|
722
|
+
self.ensure_component_tables(results_table=analysis_table)
|
|
723
|
+
else: # this is for the instance which has runtime components that we'll check
|
|
724
|
+
self.ensure_component_tables_in_analysis(
|
|
725
|
+
analysis, results_table=analysis_table
|
|
726
|
+
)
|
|
727
|
+
else:
|
|
728
|
+
self.warning("no analysis table found")
|
|
729
|
+
|
|
730
|
+
def get_analysis_class(self, analysis):
|
|
731
|
+
if isinstance(analysis, Analysis):
|
|
732
|
+
analysis_cls = analysis.__class__
|
|
733
|
+
elif issubclass(analysis, Analysis):
|
|
734
|
+
analysis_cls = analysis
|
|
735
|
+
else:
|
|
736
|
+
self.warning("ensure-analysis: analysis not mapped, using direct input")
|
|
737
|
+
analysis_cls = analysis
|
|
738
|
+
return analysis_cls
|
|
739
|
+
|
|
740
|
+
def upload_analysis(self, analysis, use_thread=False):
|
|
741
|
+
"""a wrapper for upload analysis with a thread selection context,
|
|
742
|
+
:returns: a thread reference, which you can join after setting other work.
|
|
743
|
+
"""
|
|
744
|
+
|
|
745
|
+
if use_thread:
|
|
746
|
+
thread = Thread(target=self._upload_analysis, args=(analysis,))
|
|
747
|
+
thread.start()
|
|
748
|
+
return thread
|
|
749
|
+
|
|
750
|
+
return self._upload_analysis(analysis)
|
|
751
|
+
|
|
752
|
+
def _upload_analysis(self, analysis):
|
|
753
|
+
"""the general method here we're using is to precompute the analysis row id, then batch the inserts after
|
|
754
|
+
forcing the correct primary key"""
|
|
755
|
+
self.info(f"upload analysis: {analysis}")
|
|
756
|
+
|
|
757
|
+
class AvoidDuplicateAbortUpload(Exception):
|
|
758
|
+
pass
|
|
759
|
+
|
|
760
|
+
try:
|
|
761
|
+
|
|
762
|
+
def gen(table):
|
|
763
|
+
for item in table:
|
|
764
|
+
yield item
|
|
765
|
+
|
|
766
|
+
# analysis must be solved!
|
|
767
|
+
assert isinstance(analysis, Analysis)
|
|
768
|
+
assert analysis.solved
|
|
769
|
+
|
|
770
|
+
acmpcls = analysis.__class__
|
|
771
|
+
if acmpcls not in self.mapped_classes:
|
|
772
|
+
self.ensure_analysis(analysis)
|
|
773
|
+
|
|
774
|
+
adbcls = self.mapped_classes[acmpcls]
|
|
775
|
+
|
|
776
|
+
rds = analysis.recursive_data_structure()
|
|
777
|
+
data_gens = {}
|
|
778
|
+
|
|
779
|
+
main_gen = gen(analysis.TABLE)
|
|
780
|
+
|
|
781
|
+
for lvl, comps in rds.items():
|
|
782
|
+
if lvl > 0:
|
|
783
|
+
for comp in comps:
|
|
784
|
+
cmp = comp["conf"]
|
|
785
|
+
data_gens[self.mapped_classes[cmp.__class__]] = gen(cmp.TABLE)
|
|
786
|
+
|
|
787
|
+
# with self.db.scoped_session() as sesh:
|
|
788
|
+
any_succeeded = True
|
|
789
|
+
last_values = {}
|
|
790
|
+
inx = 0
|
|
791
|
+
main_result = None
|
|
792
|
+
|
|
793
|
+
while any_succeeded and inx < analysis.index:
|
|
794
|
+
any_succeeded = False # guilty until proven innocent!!
|
|
795
|
+
|
|
796
|
+
try:
|
|
797
|
+
data_dict = next(main_gen)
|
|
798
|
+
last_values[adbcls] = data_dict
|
|
799
|
+
main_result = adbcls(None, **data_dict)
|
|
800
|
+
|
|
801
|
+
except StopIteration:
|
|
802
|
+
data_dict = last_values[adbcls]
|
|
803
|
+
main_result = adbcls(None, **data_dict)
|
|
804
|
+
|
|
805
|
+
else:
|
|
806
|
+
any_succeeded = True # here's ur fricken evidence ur honor
|
|
807
|
+
|
|
808
|
+
with self.db.session_scope() as sesh:
|
|
809
|
+
sesh.add(main_result)
|
|
810
|
+
|
|
811
|
+
sesh.commit()
|
|
812
|
+
result_id = int(main_result.id)
|
|
813
|
+
|
|
814
|
+
main_attrs = list(main_result.attr_store.values())
|
|
815
|
+
for item in main_attrs:
|
|
816
|
+
item.result_id = result_id
|
|
817
|
+
|
|
818
|
+
others = []
|
|
819
|
+
for dbclass, generator in data_gens.items():
|
|
820
|
+
try:
|
|
821
|
+
data_dict = next(generator)
|
|
822
|
+
last_values[dbclass] = data_dict
|
|
823
|
+
data_dict["result_id"] = result_id
|
|
824
|
+
cmp_result = dbclass(result_id, **data_dict)
|
|
825
|
+
others.append(cmp_result)
|
|
826
|
+
|
|
827
|
+
except StopIteration:
|
|
828
|
+
data_dict = last_values[dbclass]
|
|
829
|
+
|
|
830
|
+
data_dict["result_id"] = result_id
|
|
831
|
+
cmp_result = dbclass(result_id, **data_dict)
|
|
832
|
+
others.append(cmp_result)
|
|
833
|
+
|
|
834
|
+
else:
|
|
835
|
+
any_succeeded = True # here's ur fricken evidence ur honor
|
|
836
|
+
|
|
837
|
+
# the scoped session rolls back anything created this time :)
|
|
838
|
+
if not any_succeeded:
|
|
839
|
+
raise AvoidDuplicateAbortUpload()
|
|
840
|
+
|
|
841
|
+
with self.db.session_scope() as sesh:
|
|
842
|
+
add_list = (
|
|
843
|
+
main_attrs
|
|
844
|
+
+ others
|
|
845
|
+
+ flatten([list(item.attr_store.values()) for item in others])
|
|
846
|
+
)
|
|
847
|
+
|
|
848
|
+
sesh.add_all(add_list)
|
|
849
|
+
|
|
850
|
+
inx += (
|
|
851
|
+
1 # index += 1 is done at end of analysis so we should model that
|
|
852
|
+
)
|
|
853
|
+
|
|
854
|
+
except AvoidDuplicateAbortUpload:
|
|
855
|
+
pass # this is fine
|
|
856
|
+
|
|
857
|
+
except Exception as e:
|
|
858
|
+
self.error(e, "Issue Uploading Data")
|
|
859
|
+
|
|
860
|
+
|
|
861
|
+
# These items are registry related for the dynamic mapping
|
|
862
|
+
|
|
863
|
+
|
|
864
|
+
class MappedItem(ReportBase):
|
|
865
|
+
__abstract__ = True
|
|
866
|
+
|
|
867
|
+
_mapped_component = None
|
|
868
|
+
|
|
869
|
+
id = Column(Integer, primary_key=True)
|
|
870
|
+
tablename = Column(String(DEFAULT_STRING_LENGTH))
|
|
871
|
+
attrtable = Column(String(DEFAULT_STRING_LENGTH))
|
|
872
|
+
classname = Column(String(DEFAULT_STRING_LENGTH))
|
|
873
|
+
mro_inheritance = Column(String(1200))
|
|
874
|
+
|
|
875
|
+
active = Column(Boolean(), default=True)
|
|
876
|
+
|
|
877
|
+
def __init__(self, tablerep, mapped_class):
|
|
878
|
+
self.classname = mapped_class.__name__
|
|
879
|
+
self.tablename = tablerep.__tablename__
|
|
880
|
+
|
|
881
|
+
if issubclass(mapped_class, Analysis):
|
|
882
|
+
self.attrtable = f"anly_attr_{tablerep.__tablename__}"
|
|
883
|
+
else:
|
|
884
|
+
self.attrtable = tablerep.__tablename__.replace("tbl", "attr")
|
|
885
|
+
|
|
886
|
+
self._mapped_component = mapped_class
|
|
887
|
+
|
|
888
|
+
distinct = mapped_class.mro()
|
|
889
|
+
if mapped_class in distinct:
|
|
890
|
+
distinct.remove(mapped_class)
|
|
891
|
+
comp_inx = distinct.index(Component)
|
|
892
|
+
self.mro_inheritance = ".".join(
|
|
893
|
+
[cls.__name__ for cls in distinct[0 : comp_inx + 1]]
|
|
894
|
+
)
|
|
895
|
+
|
|
896
|
+
@classmethod
|
|
897
|
+
def db_columns(cls):
|
|
898
|
+
if "__table__" in cls.__dict__ and cls.__table__ is not None:
|
|
899
|
+
return set(list(cls.__table__.c.keys()))
|
|
900
|
+
|
|
901
|
+
@classmethod
|
|
902
|
+
def all_fields(cls):
|
|
903
|
+
all_table_fields = set(
|
|
904
|
+
[attr.lower() for attr in cls._mapped_component.cls_all_property_labels()]
|
|
905
|
+
)
|
|
906
|
+
all_attr_fields = set(
|
|
907
|
+
[
|
|
908
|
+
attr.lower()
|
|
909
|
+
for attr in cls._mapped_component.cls_all_attrs_fields().keys()
|
|
910
|
+
]
|
|
911
|
+
)
|
|
912
|
+
all_fields = set.union(all_table_fields, all_attr_fields)
|
|
913
|
+
return set(list(all_fields))
|
|
914
|
+
|
|
915
|
+
@classmethod
|
|
916
|
+
def dynamic_columns(cls):
|
|
917
|
+
if cls._mapped_component is None:
|
|
918
|
+
return []
|
|
919
|
+
|
|
920
|
+
return set.difference(cls.all_fields(), cls.db_columns())
|
|
921
|
+
|
|
922
|
+
@classmethod
|
|
923
|
+
def tablenames(cls, db):
|
|
924
|
+
# rr = ResultsRegistry()
|
|
925
|
+
with db.session_scope() as sesh:
|
|
926
|
+
results = list(sesh.query(cls.tablename).all())
|
|
927
|
+
return flatten(results)
|
|
928
|
+
|
|
929
|
+
|
|
930
|
+
class AnalysisRegistry(MappedItem):
|
|
931
|
+
__tablename__ = "analysis_registry"
|
|
932
|
+
|
|
933
|
+
|
|
934
|
+
class ComponentRegistry(MappedItem):
|
|
935
|
+
__tablename__ = "component_registry"
|