pydpm_xl 0.1.39rc31__py3-none-any.whl → 0.2.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.
- py_dpm/__init__.py +1 -1
- py_dpm/api/__init__.py +58 -189
- py_dpm/api/dpm/__init__.py +20 -0
- py_dpm/api/{data_dictionary.py → dpm/data_dictionary.py} +903 -984
- py_dpm/api/dpm/explorer.py +236 -0
- py_dpm/api/dpm/hierarchical_queries.py +142 -0
- py_dpm/api/{migration.py → dpm/migration.py} +16 -19
- py_dpm/api/{operation_scopes.py → dpm/operation_scopes.py} +319 -267
- py_dpm/api/dpm_xl/__init__.py +25 -0
- py_dpm/api/{ast_generator.py → dpm_xl/ast_generator.py} +3 -3
- py_dpm/api/{complete_ast.py → dpm_xl/complete_ast.py} +192 -168
- py_dpm/api/dpm_xl/semantic.py +354 -0
- py_dpm/api/{syntax.py → dpm_xl/syntax.py} +6 -5
- py_dpm/api/explorer.py +4 -0
- py_dpm/api/semantic.py +30 -306
- py_dpm/cli/__init__.py +9 -0
- py_dpm/{client.py → cli/main.py} +8 -8
- py_dpm/dpm/__init__.py +11 -0
- py_dpm/{models.py → dpm/models.py} +112 -88
- py_dpm/dpm/queries/base.py +100 -0
- py_dpm/dpm/queries/basic_objects.py +33 -0
- py_dpm/dpm/queries/explorer_queries.py +352 -0
- py_dpm/dpm/queries/filters.py +139 -0
- py_dpm/dpm/queries/glossary.py +45 -0
- py_dpm/dpm/queries/hierarchical_queries.py +838 -0
- py_dpm/dpm/queries/tables.py +133 -0
- py_dpm/dpm/utils.py +356 -0
- py_dpm/dpm_xl/__init__.py +8 -0
- py_dpm/dpm_xl/ast/__init__.py +14 -0
- py_dpm/{AST/ASTConstructor.py → dpm_xl/ast/constructor.py} +6 -6
- py_dpm/{AST/MLGeneration.py → dpm_xl/ast/ml_generation.py} +137 -87
- py_dpm/{AST/ModuleAnalyzer.py → dpm_xl/ast/module_analyzer.py} +7 -7
- py_dpm/{AST/ModuleDependencies.py → dpm_xl/ast/module_dependencies.py} +56 -41
- py_dpm/{AST/ASTObjects.py → dpm_xl/ast/nodes.py} +1 -1
- py_dpm/{AST/check_operands.py → dpm_xl/ast/operands.py} +16 -13
- py_dpm/{AST/ASTTemplate.py → dpm_xl/ast/template.py} +2 -2
- py_dpm/{AST/WhereClauseChecker.py → dpm_xl/ast/where_clause.py} +2 -2
- py_dpm/dpm_xl/grammar/__init__.py +18 -0
- py_dpm/dpm_xl/operators/__init__.py +19 -0
- py_dpm/{Operators/AggregateOperators.py → dpm_xl/operators/aggregate.py} +7 -7
- py_dpm/{Operators/NumericOperators.py → dpm_xl/operators/arithmetic.py} +6 -6
- py_dpm/{Operators/Operator.py → dpm_xl/operators/base.py} +5 -5
- py_dpm/{Operators/BooleanOperators.py → dpm_xl/operators/boolean.py} +5 -5
- py_dpm/{Operators/ClauseOperators.py → dpm_xl/operators/clause.py} +8 -8
- py_dpm/{Operators/ComparisonOperators.py → dpm_xl/operators/comparison.py} +5 -5
- py_dpm/{Operators/ConditionalOperators.py → dpm_xl/operators/conditional.py} +7 -7
- py_dpm/{Operators/StringOperators.py → dpm_xl/operators/string.py} +5 -5
- py_dpm/{Operators/TimeOperators.py → dpm_xl/operators/time.py} +6 -6
- py_dpm/{semantics/SemanticAnalyzer.py → dpm_xl/semantic_analyzer.py} +168 -68
- py_dpm/{semantics/Symbols.py → dpm_xl/symbols.py} +3 -3
- py_dpm/dpm_xl/types/__init__.py +13 -0
- py_dpm/{DataTypes/TypePromotion.py → dpm_xl/types/promotion.py} +2 -2
- py_dpm/{DataTypes/ScalarTypes.py → dpm_xl/types/scalar.py} +2 -2
- py_dpm/dpm_xl/utils/__init__.py +14 -0
- py_dpm/{data_handlers.py → dpm_xl/utils/data_handlers.py} +2 -2
- py_dpm/{Utils → dpm_xl/utils}/operands_mapping.py +1 -1
- py_dpm/{Utils → dpm_xl/utils}/operator_mapping.py +8 -8
- py_dpm/{OperationScopes/OperationScopeService.py → dpm_xl/utils/scopes_calculator.py} +148 -58
- py_dpm/{Utils/ast_serialization.py → dpm_xl/utils/serialization.py} +2 -2
- py_dpm/dpm_xl/validation/__init__.py +12 -0
- py_dpm/{Utils/ValidationsGenerationUtils.py → dpm_xl/validation/generation_utils.py} +2 -3
- py_dpm/{ValidationsGeneration/PropertiesConstraintsProcessor.py → dpm_xl/validation/property_constraints.py} +56 -21
- py_dpm/{ValidationsGeneration/auxiliary_functions.py → dpm_xl/validation/utils.py} +2 -2
- py_dpm/{ValidationsGeneration/VariantsProcessor.py → dpm_xl/validation/variants.py} +149 -55
- py_dpm/exceptions/__init__.py +23 -0
- py_dpm/{Exceptions → exceptions}/exceptions.py +7 -2
- pydpm_xl-0.2.0.dist-info/METADATA +278 -0
- pydpm_xl-0.2.0.dist-info/RECORD +88 -0
- pydpm_xl-0.2.0.dist-info/entry_points.txt +2 -0
- py_dpm/Exceptions/__init__.py +0 -0
- py_dpm/OperationScopes/__init__.py +0 -0
- py_dpm/Operators/__init__.py +0 -0
- py_dpm/Utils/__init__.py +0 -0
- py_dpm/Utils/utils.py +0 -2
- py_dpm/ValidationsGeneration/Utils.py +0 -364
- py_dpm/ValidationsGeneration/__init__.py +0 -0
- py_dpm/api/data_dictionary_validation.py +0 -614
- py_dpm/db_utils.py +0 -221
- py_dpm/grammar/__init__.py +0 -0
- py_dpm/grammar/dist/__init__.py +0 -0
- py_dpm/grammar/dpm_xlLexer.g4 +0 -437
- py_dpm/grammar/dpm_xlParser.g4 +0 -263
- py_dpm/semantics/DAG/DAGAnalyzer.py +0 -158
- py_dpm/semantics/DAG/__init__.py +0 -0
- py_dpm/semantics/__init__.py +0 -0
- py_dpm/views/data_types.sql +0 -12
- py_dpm/views/datapoints.sql +0 -65
- py_dpm/views/hierarchy_operand_reference.sql +0 -11
- py_dpm/views/hierarchy_preconditions.sql +0 -13
- py_dpm/views/hierarchy_variables.sql +0 -26
- py_dpm/views/hierarchy_variables_context.sql +0 -14
- py_dpm/views/key_components.sql +0 -18
- py_dpm/views/module_from_table.sql +0 -11
- py_dpm/views/open_keys.sql +0 -13
- py_dpm/views/operation_info.sql +0 -27
- py_dpm/views/operation_list.sql +0 -18
- py_dpm/views/operations_versions_from_module_version.sql +0 -30
- py_dpm/views/precondition_info.sql +0 -17
- py_dpm/views/report_type_operand_reference_info.sql +0 -18
- py_dpm/views/subcategory_info.sql +0 -17
- py_dpm/views/table_info.sql +0 -19
- pydpm_xl-0.1.39rc31.dist-info/METADATA +0 -53
- pydpm_xl-0.1.39rc31.dist-info/RECORD +0 -96
- pydpm_xl-0.1.39rc31.dist-info/entry_points.txt +0 -2
- /py_dpm/{AST → cli/commands}/__init__.py +0 -0
- /py_dpm/{migration.py → dpm/migration.py} +0 -0
- /py_dpm/{AST/ASTVisitor.py → dpm_xl/ast/visitor.py} +0 -0
- /py_dpm/{DataTypes → dpm_xl/grammar/generated}/__init__.py +0 -0
- /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlLexer.interp +0 -0
- /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlLexer.py +0 -0
- /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlLexer.tokens +0 -0
- /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlParser.interp +0 -0
- /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlParser.py +0 -0
- /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlParser.tokens +0 -0
- /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlParserListener.py +0 -0
- /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/dpm_xlParserVisitor.py +0 -0
- /py_dpm/{grammar/dist → dpm_xl/grammar/generated}/listeners.py +0 -0
- /py_dpm/{DataTypes/TimeClasses.py → dpm_xl/types/time.py} +0 -0
- /py_dpm/{Utils → dpm_xl/utils}/tokens.py +0 -0
- /py_dpm/{Exceptions → exceptions}/messages.py +0 -0
- {pydpm_xl-0.1.39rc31.dist-info → pydpm_xl-0.2.0.dist-info}/WHEEL +0 -0
- {pydpm_xl-0.1.39rc31.dist-info → pydpm_xl-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {pydpm_xl-0.1.39rc31.dist-info → pydpm_xl-0.2.0.dist-info}/top_level.txt +0 -0
|
@@ -1,984 +1,903 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Data Dictionary API
|
|
3
|
-
|
|
4
|
-
This module provides ORM-based query methods for accessing the data dictionary.
|
|
5
|
-
All methods use SQLAlchemy ORM instead of raw SQL for PostgreSQL compatibility.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from typing import List, Optional, Dict, Tuple, Any
|
|
9
|
-
from sqlalchemy import and_, or_, func, distinct, text
|
|
10
|
-
from sqlalchemy.orm import Session
|
|
11
|
-
|
|
12
|
-
from py_dpm.
|
|
13
|
-
from py_dpm.models import (
|
|
14
|
-
ViewDatapoints,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
if release_id is not None:
|
|
382
|
-
query = query.filter(
|
|
383
|
-
or_(
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
)
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
)
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
or_(
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
)
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
).
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
"
|
|
735
|
-
"
|
|
736
|
-
"
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
)
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
if
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
or_(
|
|
905
|
-
subq.c.end_release.is_(None),
|
|
906
|
-
subq.c.end_release > release_id
|
|
907
|
-
),
|
|
908
|
-
subq.c.start_release <= release_id
|
|
909
|
-
)
|
|
910
|
-
|
|
911
|
-
return query.scalar() or 0
|
|
912
|
-
|
|
913
|
-
# ==================== Module and Table Query Methods ====================
|
|
914
|
-
|
|
915
|
-
def get_all_variables_for_table(self, table_vid: int) -> Dict[str, str]:
|
|
916
|
-
"""
|
|
917
|
-
Get all variables for a table version.
|
|
918
|
-
|
|
919
|
-
Queries SOURCE_DB via TableVersionCell -> VariableVersion -> Property -> DataType
|
|
920
|
-
to get all variable IDs with their single-char type codes.
|
|
921
|
-
|
|
922
|
-
Args:
|
|
923
|
-
table_vid: Table version ID
|
|
924
|
-
|
|
925
|
-
Returns:
|
|
926
|
-
Dictionary mapping variable_id (str) to type_code (str)
|
|
927
|
-
Type codes are single characters from DataType.Code (e.g., 'm', 'e', 'b', 'p')
|
|
928
|
-
"""
|
|
929
|
-
query = self.session.query(
|
|
930
|
-
Variable.variableid,
|
|
931
|
-
DataType.code
|
|
932
|
-
).select_from(TableVersionCell).join(
|
|
933
|
-
VariableVersion, TableVersionCell.variablevid == VariableVersion.variablevid
|
|
934
|
-
).join(
|
|
935
|
-
Variable, VariableVersion.variableid == Variable.variableid
|
|
936
|
-
).join(
|
|
937
|
-
Property, VariableVersion.propertyid == Property.propertyid
|
|
938
|
-
).join(
|
|
939
|
-
DataType, Property.datatypeid == DataType.datatypeid
|
|
940
|
-
).filter(
|
|
941
|
-
TableVersionCell.tablevid == table_vid
|
|
942
|
-
).distinct()
|
|
943
|
-
|
|
944
|
-
results = query.all()
|
|
945
|
-
# IMPORTANT: Convert to int first to avoid ".0" suffix from potential float values
|
|
946
|
-
return {str(int(r.variableid)): r.code for r in results if r.code is not None}
|
|
947
|
-
|
|
948
|
-
def get_all_tables_for_module(self, module_vid: int) -> List[Dict[str, Any]]:
|
|
949
|
-
"""
|
|
950
|
-
Get ALL tables belonging to a module version.
|
|
951
|
-
|
|
952
|
-
Queries SOURCE_DB via ModuleVersionComposition to find all tables
|
|
953
|
-
in a module, regardless of whether they're referenced in validations.
|
|
954
|
-
|
|
955
|
-
Args:
|
|
956
|
-
module_vid: Module version ID
|
|
957
|
-
|
|
958
|
-
Returns:
|
|
959
|
-
List of dicts with table_vid, table_code, table_name
|
|
960
|
-
"""
|
|
961
|
-
query = self.session.query(
|
|
962
|
-
TableVersion.tablevid,
|
|
963
|
-
TableVersion.code,
|
|
964
|
-
TableVersion.name
|
|
965
|
-
).select_from(ModuleVersionComposition).join(
|
|
966
|
-
TableVersion, ModuleVersionComposition.tablevid == TableVersion.tablevid
|
|
967
|
-
).filter(
|
|
968
|
-
ModuleVersionComposition.modulevid == module_vid
|
|
969
|
-
).distinct().order_by(TableVersion.code)
|
|
970
|
-
|
|
971
|
-
results = query.all()
|
|
972
|
-
return [
|
|
973
|
-
{
|
|
974
|
-
"table_vid": r.tablevid,
|
|
975
|
-
"table_code": r.code,
|
|
976
|
-
"table_name": r.name
|
|
977
|
-
}
|
|
978
|
-
for r in results
|
|
979
|
-
]
|
|
980
|
-
|
|
981
|
-
def __del__(self):
|
|
982
|
-
"""Clean up resources."""
|
|
983
|
-
if hasattr(self, 'session') and self.session:
|
|
984
|
-
self.session.close()
|
|
1
|
+
"""
|
|
2
|
+
Data Dictionary API
|
|
3
|
+
|
|
4
|
+
This module provides ORM-based query methods for accessing the data dictionary.
|
|
5
|
+
All methods use SQLAlchemy ORM instead of raw SQL for PostgreSQL compatibility.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import List, Optional, Dict, Tuple, Any
|
|
9
|
+
from sqlalchemy import and_, or_, func, distinct, text
|
|
10
|
+
from sqlalchemy.orm import Session
|
|
11
|
+
|
|
12
|
+
from py_dpm.dpm.utils import get_session, get_engine
|
|
13
|
+
from py_dpm.dpm.models import (
|
|
14
|
+
ViewDatapoints,
|
|
15
|
+
TableVersion,
|
|
16
|
+
ItemCategory,
|
|
17
|
+
Cell,
|
|
18
|
+
Property,
|
|
19
|
+
DataType,
|
|
20
|
+
KeyComposition,
|
|
21
|
+
VariableVersion,
|
|
22
|
+
Variable,
|
|
23
|
+
Category,
|
|
24
|
+
PropertyCategory,
|
|
25
|
+
ModuleVersion,
|
|
26
|
+
ModuleVersionComposition,
|
|
27
|
+
Release,
|
|
28
|
+
Header,
|
|
29
|
+
HeaderVersion,
|
|
30
|
+
TableVersionHeader,
|
|
31
|
+
TableVersionCell,
|
|
32
|
+
)
|
|
33
|
+
from py_dpm.dpm.queries.tables import TableQuery
|
|
34
|
+
from py_dpm.dpm.queries.glossary import ItemQuery
|
|
35
|
+
from py_dpm.dpm.queries.basic_objects import ReleaseQuery
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class DataDictionaryAPI:
|
|
39
|
+
"""
|
|
40
|
+
Main API for querying the data dictionary using ORM.
|
|
41
|
+
|
|
42
|
+
This class provides methods for:
|
|
43
|
+
- Table/row/column reference lookups
|
|
44
|
+
- Wildcard resolution
|
|
45
|
+
- Item and sheet validation
|
|
46
|
+
- Open key queries
|
|
47
|
+
- Metadata retrieval
|
|
48
|
+
|
|
49
|
+
All methods use SQLAlchemy ORM for database-agnostic queries.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(
|
|
53
|
+
self, database_path: Optional[str] = None, connection_url: Optional[str] = None
|
|
54
|
+
):
|
|
55
|
+
"""
|
|
56
|
+
Initialize the Data Dictionary API.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
database_path: Path to SQLite database (optional)
|
|
60
|
+
connection_url: SQLAlchemy connection URL for PostgreSQL (optional)
|
|
61
|
+
"""
|
|
62
|
+
engine = get_engine(database_path=database_path, connection_url=connection_url)
|
|
63
|
+
self.session = get_session()
|
|
64
|
+
|
|
65
|
+
# ==================== Release Query Methods ====================
|
|
66
|
+
|
|
67
|
+
def get_releases(self) -> List[Dict[str, Any]]:
|
|
68
|
+
"""
|
|
69
|
+
Fetch list of available releases from database.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
List of dictionaries containing release info
|
|
73
|
+
"""
|
|
74
|
+
# Use ReleaseQuery
|
|
75
|
+
query = ReleaseQuery.get_all_releases(self.session)
|
|
76
|
+
return query.to_dict()
|
|
77
|
+
|
|
78
|
+
def get_release_by_id(self, release_id: int) -> Optional[Dict[str, Any]]:
|
|
79
|
+
"""
|
|
80
|
+
Fetch a specific release by its ID.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
release_id: The ID of the release to fetch
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Dictionary with release info or None if not found
|
|
87
|
+
"""
|
|
88
|
+
query = ReleaseQuery.get_release_by_id(self.session, release_id)
|
|
89
|
+
result = query.to_dict()
|
|
90
|
+
return result[0] if result else None
|
|
91
|
+
|
|
92
|
+
def get_release_by_code(self, release_code: str) -> Optional[Dict[str, Any]]:
|
|
93
|
+
"""
|
|
94
|
+
Fetch a specific release by its code.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
release_code: The code of the release to fetch
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Dictionary with release info or None if not found
|
|
101
|
+
"""
|
|
102
|
+
query = ReleaseQuery.get_release_by_code(self.session, release_code)
|
|
103
|
+
result = query.to_dict()
|
|
104
|
+
return result[0] if result else None
|
|
105
|
+
|
|
106
|
+
# ==================== Reference Query Methods ====================
|
|
107
|
+
|
|
108
|
+
def get_tables(
|
|
109
|
+
self,
|
|
110
|
+
release_id: Optional[int] = None,
|
|
111
|
+
date: Optional[str] = None,
|
|
112
|
+
release_code: Optional[str] = None,
|
|
113
|
+
) -> List[str]:
|
|
114
|
+
"""
|
|
115
|
+
Get all available table codes from TableVersion.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
release_id: Optional release ID to filter by
|
|
119
|
+
date: Optional date string (YYYY-MM-DD) to filter by. Mutually exclusive with release_id.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
List of table codes
|
|
123
|
+
"""
|
|
124
|
+
# Use TableQuery
|
|
125
|
+
query = TableQuery.get_tables(self.session, release_id, date, release_code)
|
|
126
|
+
|
|
127
|
+
return query.to_dict()
|
|
128
|
+
|
|
129
|
+
def get_available_tables_from_datapoints(
|
|
130
|
+
self, release_id: Optional[int] = None
|
|
131
|
+
) -> List[str]:
|
|
132
|
+
"""
|
|
133
|
+
Get all available table codes from datapoints.
|
|
134
|
+
Always uses ViewDatapoints class methods for database compatibility.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
release_id: Optional release ID to filter by
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
List of table codes
|
|
141
|
+
"""
|
|
142
|
+
# Use TableQuery
|
|
143
|
+
query = TableQuery.get_available_tables_from_datapoints(
|
|
144
|
+
self.session, release_id
|
|
145
|
+
)
|
|
146
|
+
return query.to_dict()
|
|
147
|
+
|
|
148
|
+
def get_available_rows(
|
|
149
|
+
self, table_code: str, release_id: Optional[int] = None
|
|
150
|
+
) -> List[str]:
|
|
151
|
+
"""
|
|
152
|
+
Get all available row codes for a table.
|
|
153
|
+
Always uses ViewDatapoints class methods for database compatibility.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
table_code: Table code to query
|
|
157
|
+
release_id: Optional release ID to filter by
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
List of row codes
|
|
161
|
+
"""
|
|
162
|
+
# Use TableQuery
|
|
163
|
+
query = TableQuery.get_available_rows(self.session, table_code, release_id)
|
|
164
|
+
return query.to_dict()
|
|
165
|
+
|
|
166
|
+
def get_available_columns(
|
|
167
|
+
self, table_code: str, release_id: Optional[int] = None
|
|
168
|
+
) -> List[str]:
|
|
169
|
+
"""
|
|
170
|
+
Get all available column codes for a table.
|
|
171
|
+
Always uses ViewDatapoints class methods for database compatibility.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
table_code: Table code to query
|
|
175
|
+
release_id: Optional release ID to filter by
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
List of column codes
|
|
179
|
+
"""
|
|
180
|
+
# Use TableQuery
|
|
181
|
+
query = TableQuery.get_available_columns(self.session, table_code, release_id)
|
|
182
|
+
return query.to_dict()
|
|
183
|
+
|
|
184
|
+
# ==================== Item Query Methods ====================
|
|
185
|
+
|
|
186
|
+
def get_all_item_signatures(self, release_id: Optional[int] = None) -> List[str]:
|
|
187
|
+
"""
|
|
188
|
+
Get all active item signatures from ItemCategory.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
release_id: Optional release ID to filter by
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
List of item signatures
|
|
195
|
+
"""
|
|
196
|
+
# Use ItemQuery
|
|
197
|
+
query = ItemQuery.get_all_item_signatures(self.session, release_id)
|
|
198
|
+
return query.to_dict()
|
|
199
|
+
|
|
200
|
+
def get_item_categories(
|
|
201
|
+
self, release_id: Optional[int] = None
|
|
202
|
+
) -> List[Tuple[str, str]]:
|
|
203
|
+
"""
|
|
204
|
+
Get all item categories with code and signature.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
release_id: Optional release ID to filter by
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
List of tuples (code, signature)
|
|
211
|
+
"""
|
|
212
|
+
# Use ItemQuery
|
|
213
|
+
query = ItemQuery.get_item_categories(self.session, release_id)
|
|
214
|
+
return query.to_dict()
|
|
215
|
+
|
|
216
|
+
# ==================== Sheet Query Methods ====================
|
|
217
|
+
|
|
218
|
+
def table_has_sheets(
|
|
219
|
+
self, table_code: str, release_id: Optional[int] = None
|
|
220
|
+
) -> bool:
|
|
221
|
+
"""
|
|
222
|
+
Check if a table has any sheets defined.
|
|
223
|
+
Always uses ViewDatapoints class methods for database compatibility.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
table_code: Table code to check
|
|
227
|
+
release_id: Optional release ID to filter by
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
True if table has sheets, False otherwise
|
|
231
|
+
"""
|
|
232
|
+
# Use ViewDatapoints class method (works for both SQLite and PostgreSQL)
|
|
233
|
+
base_query = ViewDatapoints.create_view_query(self.session)
|
|
234
|
+
subq = base_query.subquery()
|
|
235
|
+
|
|
236
|
+
query = self.session.query(subq).filter(
|
|
237
|
+
subq.c.table_code == table_code,
|
|
238
|
+
subq.c.sheet_code.isnot(None),
|
|
239
|
+
subq.c.sheet_code != "",
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
if release_id is not None:
|
|
243
|
+
query = query.filter(
|
|
244
|
+
or_(subq.c.end_release.is_(None), subq.c.end_release > release_id),
|
|
245
|
+
subq.c.start_release <= release_id,
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
count = query.with_entities(func.count()).scalar()
|
|
249
|
+
return count > 0
|
|
250
|
+
|
|
251
|
+
def get_available_sheets(
|
|
252
|
+
self, table_code: str, release_id: Optional[int] = None
|
|
253
|
+
) -> List[str]:
|
|
254
|
+
"""
|
|
255
|
+
Get all available sheet codes for a table.
|
|
256
|
+
Always uses ViewDatapoints class methods for database compatibility.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
table_code: Table code to query
|
|
260
|
+
release_id: Optional release ID to filter by
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
List of sheet codes
|
|
264
|
+
"""
|
|
265
|
+
# Use ViewDatapoints class method (works for both SQLite and PostgreSQL)
|
|
266
|
+
base_query = ViewDatapoints.create_view_query(self.session)
|
|
267
|
+
subq = base_query.subquery()
|
|
268
|
+
|
|
269
|
+
# Use TableQuery
|
|
270
|
+
query = TableQuery.get_available_sheets(self.session, table_code, release_id)
|
|
271
|
+
return query.to_dict()
|
|
272
|
+
|
|
273
|
+
def check_cell_exists(
|
|
274
|
+
self,
|
|
275
|
+
table_code: str,
|
|
276
|
+
row_code: Optional[str] = None,
|
|
277
|
+
column_code: Optional[str] = None,
|
|
278
|
+
sheet_code: Optional[str] = None,
|
|
279
|
+
release_id: Optional[int] = None,
|
|
280
|
+
) -> bool:
|
|
281
|
+
"""
|
|
282
|
+
Check if a cell reference exists in the datapoints.
|
|
283
|
+
Always uses ViewDatapoints class methods for database compatibility.
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
table_code: Table code
|
|
287
|
+
row_code: Optional row code
|
|
288
|
+
column_code: Optional column code
|
|
289
|
+
sheet_code: Optional sheet code
|
|
290
|
+
release_id: Optional release ID to filter by
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
True if cell exists, False otherwise
|
|
294
|
+
"""
|
|
295
|
+
# Use ViewDatapoints class method (works for both SQLite and PostgreSQL)
|
|
296
|
+
base_query = ViewDatapoints.create_view_query(self.session)
|
|
297
|
+
subq = base_query.subquery()
|
|
298
|
+
|
|
299
|
+
query = self.session.query(subq).filter(subq.c.table_code == table_code)
|
|
300
|
+
|
|
301
|
+
if row_code:
|
|
302
|
+
query = query.filter(subq.c.row_code == row_code)
|
|
303
|
+
|
|
304
|
+
if column_code:
|
|
305
|
+
query = query.filter(subq.c.column_code == column_code)
|
|
306
|
+
|
|
307
|
+
if sheet_code:
|
|
308
|
+
query = query.filter(subq.c.sheet_code == sheet_code)
|
|
309
|
+
|
|
310
|
+
if release_id is not None:
|
|
311
|
+
query = query.filter(
|
|
312
|
+
or_(subq.c.end_release.is_(None), subq.c.end_release > release_id),
|
|
313
|
+
subq.c.start_release <= release_id,
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
count = query.with_entities(func.count()).scalar()
|
|
317
|
+
return count > 0
|
|
318
|
+
|
|
319
|
+
def get_table_dimensions(
|
|
320
|
+
self, table_code: str, release_id: Optional[int] = None
|
|
321
|
+
) -> List[str]:
|
|
322
|
+
"""
|
|
323
|
+
Get dimension codes for a table from KeyComposition.
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
table_code: Table code to query
|
|
327
|
+
release_id: Optional release ID to filter by
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
List of dimension codes
|
|
331
|
+
"""
|
|
332
|
+
query = (
|
|
333
|
+
self.session.query(distinct(ItemCategory.code))
|
|
334
|
+
.select_from(TableVersion)
|
|
335
|
+
.join(KeyComposition, TableVersion.keyid == KeyComposition.keyid)
|
|
336
|
+
.join(
|
|
337
|
+
VariableVersion,
|
|
338
|
+
KeyComposition.variablevid == VariableVersion.variablevid,
|
|
339
|
+
)
|
|
340
|
+
.join(ItemCategory, VariableVersion.propertyid == ItemCategory.itemid)
|
|
341
|
+
.filter(TableVersion.code == table_code, ItemCategory.code.isnot(None))
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
if release_id is not None:
|
|
345
|
+
query = query.filter(
|
|
346
|
+
or_(
|
|
347
|
+
TableVersion.endreleaseid.is_(None),
|
|
348
|
+
TableVersion.endreleaseid > release_id,
|
|
349
|
+
),
|
|
350
|
+
TableVersion.startreleaseid <= release_id,
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
results = query.order_by(ItemCategory.code).all()
|
|
354
|
+
return [r[0] for r in results]
|
|
355
|
+
|
|
356
|
+
def get_default_dimension_signature(
|
|
357
|
+
self, dimension_code: str, release_id: Optional[int] = None
|
|
358
|
+
) -> Optional[str]:
|
|
359
|
+
"""
|
|
360
|
+
Get the default signature for a dimension.
|
|
361
|
+
|
|
362
|
+
Args:
|
|
363
|
+
dimension_code: Dimension code to query
|
|
364
|
+
release_id: Optional release ID to filter by
|
|
365
|
+
|
|
366
|
+
Returns:
|
|
367
|
+
Default signature or None
|
|
368
|
+
"""
|
|
369
|
+
pattern1 = f"{dimension_code}:%"
|
|
370
|
+
pattern2 = f"%:{dimension_code}"
|
|
371
|
+
|
|
372
|
+
query = self.session.query(distinct(ItemCategory.signature)).filter(
|
|
373
|
+
ItemCategory.code.isnot(None),
|
|
374
|
+
ItemCategory.signature.isnot(None),
|
|
375
|
+
or_(
|
|
376
|
+
ItemCategory.signature.like(pattern1),
|
|
377
|
+
ItemCategory.signature.like(pattern2),
|
|
378
|
+
),
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
if release_id is not None:
|
|
382
|
+
query = query.filter(
|
|
383
|
+
or_(
|
|
384
|
+
ItemCategory.endreleaseid.is_(None),
|
|
385
|
+
ItemCategory.endreleaseid > release_id,
|
|
386
|
+
)
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
result = query.order_by(ItemCategory.signature).first()
|
|
390
|
+
return result[0] if result else None
|
|
391
|
+
|
|
392
|
+
def get_valid_sheet_code_for_dimension(
|
|
393
|
+
self,
|
|
394
|
+
dimension_code: str,
|
|
395
|
+
signature: Optional[str] = None,
|
|
396
|
+
release_id: Optional[int] = None,
|
|
397
|
+
) -> Optional[str]:
|
|
398
|
+
"""
|
|
399
|
+
Get a valid sheet code for a dimension and signature.
|
|
400
|
+
|
|
401
|
+
Args:
|
|
402
|
+
dimension_code: Dimension code to query
|
|
403
|
+
signature: Optional signature to match
|
|
404
|
+
release_id: Optional release ID to filter by
|
|
405
|
+
|
|
406
|
+
Returns:
|
|
407
|
+
Valid sheet code or None
|
|
408
|
+
"""
|
|
409
|
+
if signature:
|
|
410
|
+
pattern = f"{signature}%"
|
|
411
|
+
query = self.session.query(ItemCategory.code).filter(
|
|
412
|
+
ItemCategory.code.isnot(None),
|
|
413
|
+
ItemCategory.signature.isnot(None),
|
|
414
|
+
or_(
|
|
415
|
+
ItemCategory.signature.like(pattern),
|
|
416
|
+
ItemCategory.code == dimension_code,
|
|
417
|
+
),
|
|
418
|
+
)
|
|
419
|
+
else:
|
|
420
|
+
query = self.session.query(ItemCategory.code).filter(
|
|
421
|
+
ItemCategory.code == dimension_code
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
if release_id is not None:
|
|
425
|
+
query = query.filter(
|
|
426
|
+
or_(
|
|
427
|
+
ItemCategory.endreleaseid.is_(None),
|
|
428
|
+
ItemCategory.endreleaseid > release_id,
|
|
429
|
+
)
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
# Order by exact match first, then by code length and alphabetically
|
|
433
|
+
# Note: SQLAlchemy's case expression for ordering
|
|
434
|
+
from sqlalchemy import case, func as sa_func
|
|
435
|
+
|
|
436
|
+
result = query.order_by(
|
|
437
|
+
case((ItemCategory.code == dimension_code, 0), else_=1),
|
|
438
|
+
sa_func.length(ItemCategory.code),
|
|
439
|
+
ItemCategory.code,
|
|
440
|
+
).first()
|
|
441
|
+
|
|
442
|
+
return result[0] if result else None
|
|
443
|
+
|
|
444
|
+
# ==================== Open Key Query Methods ====================
|
|
445
|
+
|
|
446
|
+
def get_open_keys_for_table(
|
|
447
|
+
self, table_code: str, release_id: Optional[int] = None
|
|
448
|
+
) -> List[Dict[str, Any]]:
|
|
449
|
+
"""
|
|
450
|
+
Get open key information for a table.
|
|
451
|
+
|
|
452
|
+
Args:
|
|
453
|
+
table_code: Table code to query
|
|
454
|
+
release_id: Optional release ID to filter by
|
|
455
|
+
|
|
456
|
+
Returns:
|
|
457
|
+
List of dictionaries with open key info
|
|
458
|
+
"""
|
|
459
|
+
query = (
|
|
460
|
+
self.session.query(
|
|
461
|
+
TableVersion.code.label("table_version_code"),
|
|
462
|
+
ItemCategory.code.label("property_code"),
|
|
463
|
+
DataType.name.label("data_type_name"),
|
|
464
|
+
)
|
|
465
|
+
.select_from(DataType)
|
|
466
|
+
.join(Property, DataType.datatypeid == Property.datatypeid)
|
|
467
|
+
.join(ItemCategory, Property.propertyid == ItemCategory.itemid)
|
|
468
|
+
.join(VariableVersion, ItemCategory.itemid == VariableVersion.propertyid)
|
|
469
|
+
.join(
|
|
470
|
+
KeyComposition,
|
|
471
|
+
VariableVersion.variablevid == KeyComposition.variablevid,
|
|
472
|
+
)
|
|
473
|
+
.join(TableVersion, KeyComposition.keyid == TableVersion.keyid)
|
|
474
|
+
.join(
|
|
475
|
+
ModuleVersionComposition,
|
|
476
|
+
TableVersion.tablevid == ModuleVersionComposition.tablevid,
|
|
477
|
+
)
|
|
478
|
+
.join(
|
|
479
|
+
ModuleVersion,
|
|
480
|
+
ModuleVersionComposition.modulevid == ModuleVersion.modulevid,
|
|
481
|
+
)
|
|
482
|
+
.filter(TableVersion.code == table_code)
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
if release_id is not None:
|
|
486
|
+
query = query.filter(
|
|
487
|
+
or_(
|
|
488
|
+
TableVersion.endreleaseid.is_(None),
|
|
489
|
+
TableVersion.endreleaseid > release_id,
|
|
490
|
+
),
|
|
491
|
+
TableVersion.startreleaseid <= release_id,
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
results = query.distinct().order_by(TableVersion.code, ItemCategory.code).all()
|
|
495
|
+
|
|
496
|
+
return [
|
|
497
|
+
{
|
|
498
|
+
"table_version_code": r.table_version_code,
|
|
499
|
+
"property_code": r.property_code,
|
|
500
|
+
"data_type_name": r.data_type_name,
|
|
501
|
+
}
|
|
502
|
+
for r in results
|
|
503
|
+
]
|
|
504
|
+
|
|
505
|
+
def get_category_signature(
|
|
506
|
+
self, property_code: str, category_code: str, release_id: Optional[int] = None
|
|
507
|
+
) -> Optional[str]:
|
|
508
|
+
"""
|
|
509
|
+
Get the signature for a category given a property code.
|
|
510
|
+
|
|
511
|
+
Args:
|
|
512
|
+
property_code: Property code
|
|
513
|
+
category_code: Category code
|
|
514
|
+
release_id: Optional release ID to filter by
|
|
515
|
+
|
|
516
|
+
Returns:
|
|
517
|
+
Category signature or None
|
|
518
|
+
"""
|
|
519
|
+
ic_alias1 = ItemCategory
|
|
520
|
+
ic_alias2 = ItemCategory.__table__.alias("ic2")
|
|
521
|
+
|
|
522
|
+
query = (
|
|
523
|
+
self.session.query(ic_alias2.c.Signature)
|
|
524
|
+
.select_from(Property)
|
|
525
|
+
.join(ic_alias1, Property.propertyid == ic_alias1.itemid)
|
|
526
|
+
.join(PropertyCategory, Property.propertyid == PropertyCategory.propertyid)
|
|
527
|
+
.join(Category, PropertyCategory.categoryid == Category.categoryid)
|
|
528
|
+
.join(ic_alias2, Category.categoryid == ic_alias2.c.CategoryID)
|
|
529
|
+
.filter(ic_alias1.code == property_code, ic_alias2.c.Code == category_code)
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
if release_id is not None:
|
|
533
|
+
query = query.filter(
|
|
534
|
+
or_(
|
|
535
|
+
ic_alias1.endreleaseid.is_(None),
|
|
536
|
+
ic_alias1.endreleaseid > release_id,
|
|
537
|
+
),
|
|
538
|
+
or_(
|
|
539
|
+
ic_alias2.c.EndReleaseID.is_(None),
|
|
540
|
+
ic_alias2.c.EndReleaseID > release_id,
|
|
541
|
+
),
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
result = query.first()
|
|
545
|
+
return result[0] if result else None
|
|
546
|
+
|
|
547
|
+
def get_available_items_for_key(
|
|
548
|
+
self, property_code: str, release_id: Optional[int] = None
|
|
549
|
+
) -> List[Dict[str, Any]]:
|
|
550
|
+
"""
|
|
551
|
+
Get available items (code, signature) for an open key property.
|
|
552
|
+
|
|
553
|
+
Args:
|
|
554
|
+
property_code: Property code
|
|
555
|
+
release_id: Optional release ID to filter by
|
|
556
|
+
|
|
557
|
+
Returns:
|
|
558
|
+
List of dictionaries with item info (code, signature)
|
|
559
|
+
"""
|
|
560
|
+
ic_alias1 = ItemCategory
|
|
561
|
+
ic_alias2 = ItemCategory.__table__.alias("ic2")
|
|
562
|
+
|
|
563
|
+
query = (
|
|
564
|
+
self.session.query(distinct(ic_alias2.c.Code), ic_alias2.c.Signature)
|
|
565
|
+
.select_from(Property)
|
|
566
|
+
.join(ic_alias1, Property.propertyid == ic_alias1.itemid)
|
|
567
|
+
.join(PropertyCategory, Property.propertyid == PropertyCategory.propertyid)
|
|
568
|
+
.join(Category, PropertyCategory.categoryid == Category.categoryid)
|
|
569
|
+
.join(ic_alias2, Category.categoryid == ic_alias2.c.CategoryID)
|
|
570
|
+
.filter(ic_alias1.code == property_code)
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
if release_id is not None:
|
|
574
|
+
query = query.filter(
|
|
575
|
+
or_(
|
|
576
|
+
ic_alias1.endreleaseid.is_(None),
|
|
577
|
+
ic_alias1.endreleaseid > release_id,
|
|
578
|
+
),
|
|
579
|
+
or_(
|
|
580
|
+
ic_alias2.c.EndReleaseID.is_(None),
|
|
581
|
+
ic_alias2.c.EndReleaseID > release_id,
|
|
582
|
+
),
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
results = query.order_by(ic_alias2.c.Code).all()
|
|
586
|
+
return [{"code": r[0], "signature": r[1]} for r in results]
|
|
587
|
+
|
|
588
|
+
# ==================== Metadata Query Methods ====================
|
|
589
|
+
|
|
590
|
+
def get_datapoint_metadata(
|
|
591
|
+
self,
|
|
592
|
+
table_code: str,
|
|
593
|
+
row_code: str,
|
|
594
|
+
column_code: str,
|
|
595
|
+
sheet_code: Optional[str] = None,
|
|
596
|
+
release_id: Optional[int] = None,
|
|
597
|
+
) -> Optional[Dict[str, Any]]:
|
|
598
|
+
"""
|
|
599
|
+
Get metadata for a specific datapoint.
|
|
600
|
+
Always uses ViewDatapoints class methods for database compatibility.
|
|
601
|
+
|
|
602
|
+
Args:
|
|
603
|
+
table_code: Table code
|
|
604
|
+
row_code: Row code
|
|
605
|
+
column_code: Column code
|
|
606
|
+
sheet_code: Optional sheet code
|
|
607
|
+
release_id: Optional release ID to filter by
|
|
608
|
+
|
|
609
|
+
Returns:
|
|
610
|
+
Dictionary with datapoint metadata or None
|
|
611
|
+
"""
|
|
612
|
+
# Use ViewDatapoints class method (works for both SQLite and PostgreSQL)
|
|
613
|
+
base_query = ViewDatapoints.create_view_query(self.session)
|
|
614
|
+
subq = base_query.subquery()
|
|
615
|
+
|
|
616
|
+
query = self.session.query(subq).filter(
|
|
617
|
+
subq.c.table_code == table_code,
|
|
618
|
+
subq.c.row_code == row_code,
|
|
619
|
+
subq.c.column_code == column_code,
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
if sheet_code:
|
|
623
|
+
query = query.filter(subq.c.sheet_code == sheet_code)
|
|
624
|
+
|
|
625
|
+
if release_id is not None:
|
|
626
|
+
query = query.filter(
|
|
627
|
+
or_(subq.c.end_release.is_(None), subq.c.end_release > release_id),
|
|
628
|
+
subq.c.start_release <= release_id,
|
|
629
|
+
)
|
|
630
|
+
|
|
631
|
+
result = query.first()
|
|
632
|
+
|
|
633
|
+
if result:
|
|
634
|
+
return {
|
|
635
|
+
"cell_code": result.cell_code,
|
|
636
|
+
"table_code": result.table_code,
|
|
637
|
+
"row_code": result.row_code,
|
|
638
|
+
"column_code": result.column_code,
|
|
639
|
+
"sheet_code": result.sheet_code,
|
|
640
|
+
"variable_id": result.variable_id,
|
|
641
|
+
"data_type": result.data_type,
|
|
642
|
+
"table_vid": result.table_vid,
|
|
643
|
+
"property_id": result.property_id,
|
|
644
|
+
"start_release": result.start_release,
|
|
645
|
+
"end_release": result.end_release,
|
|
646
|
+
"cell_id": result.cell_id,
|
|
647
|
+
"context_id": result.context_id,
|
|
648
|
+
"variable_vid": result.variable_vid,
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
return None
|
|
652
|
+
|
|
653
|
+
def get_table_version(
|
|
654
|
+
self, table_code: str, release_id: Optional[int] = None
|
|
655
|
+
) -> Optional[Dict[str, Any]]:
|
|
656
|
+
"""
|
|
657
|
+
Get table version information.
|
|
658
|
+
|
|
659
|
+
Args:
|
|
660
|
+
table_code: Table code
|
|
661
|
+
release_id: Optional release ID to filter by
|
|
662
|
+
|
|
663
|
+
Returns:
|
|
664
|
+
Dictionary with table version info or None
|
|
665
|
+
"""
|
|
666
|
+
query = self.session.query(TableVersion.tablevid, TableVersion.code).filter(
|
|
667
|
+
TableVersion.code == table_code
|
|
668
|
+
)
|
|
669
|
+
|
|
670
|
+
if release_id is not None:
|
|
671
|
+
query = query.filter(
|
|
672
|
+
or_(
|
|
673
|
+
TableVersion.endreleaseid.is_(None),
|
|
674
|
+
TableVersion.endreleaseid > release_id,
|
|
675
|
+
),
|
|
676
|
+
TableVersion.startreleaseid <= release_id,
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
result = query.first()
|
|
680
|
+
|
|
681
|
+
if result:
|
|
682
|
+
return {
|
|
683
|
+
"table_vid": result.tablevid,
|
|
684
|
+
"code": result.code,
|
|
685
|
+
"name": "", # Not fetched in first query
|
|
686
|
+
"description": "", # Not fetched in first query
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
# Fallback with LIKE pattern
|
|
690
|
+
pattern = f"{table_code}%"
|
|
691
|
+
query = self.session.query(TableVersion.tablevid, TableVersion.code).filter(
|
|
692
|
+
TableVersion.code.like(pattern)
|
|
693
|
+
)
|
|
694
|
+
|
|
695
|
+
if release_id is not None:
|
|
696
|
+
query = query.filter(
|
|
697
|
+
or_(
|
|
698
|
+
TableVersion.endreleaseid.is_(None),
|
|
699
|
+
TableVersion.endreleaseid > release_id,
|
|
700
|
+
),
|
|
701
|
+
TableVersion.startreleaseid <= release_id,
|
|
702
|
+
)
|
|
703
|
+
|
|
704
|
+
result = query.first()
|
|
705
|
+
|
|
706
|
+
if result:
|
|
707
|
+
return {
|
|
708
|
+
"table_vid": result.tablevid,
|
|
709
|
+
"code": result.code,
|
|
710
|
+
"name": "", # Not fetched in fallback
|
|
711
|
+
"description": "", # Not fetched in fallback
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
return None
|
|
715
|
+
|
|
716
|
+
def get_release_by_code(self, release_code: str) -> Optional[Dict[str, Any]]:
|
|
717
|
+
"""
|
|
718
|
+
Get release information by code.
|
|
719
|
+
|
|
720
|
+
Args:
|
|
721
|
+
release_code: Release code
|
|
722
|
+
|
|
723
|
+
Returns:
|
|
724
|
+
Dictionary with release info or None
|
|
725
|
+
"""
|
|
726
|
+
result = (
|
|
727
|
+
self.session.query(Release.releaseid, Release.code, Release.date)
|
|
728
|
+
.filter(Release.code == release_code)
|
|
729
|
+
.first()
|
|
730
|
+
)
|
|
731
|
+
|
|
732
|
+
if result:
|
|
733
|
+
return {
|
|
734
|
+
"ReleaseID": result.releaseid,
|
|
735
|
+
"code": result.code,
|
|
736
|
+
"date": result.date,
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
return None
|
|
740
|
+
|
|
741
|
+
def get_latest_release(self) -> Optional[Dict[str, Any]]:
|
|
742
|
+
"""
|
|
743
|
+
Get the latest released version.
|
|
744
|
+
|
|
745
|
+
Returns:
|
|
746
|
+
Dictionary with latest release info or None
|
|
747
|
+
"""
|
|
748
|
+
result = (
|
|
749
|
+
self.session.query(Release.code, Release.date)
|
|
750
|
+
.filter(Release.status == "released")
|
|
751
|
+
.order_by(Release.date.desc())
|
|
752
|
+
.first()
|
|
753
|
+
)
|
|
754
|
+
|
|
755
|
+
if result:
|
|
756
|
+
return {"code": result.code, "date": result.date}
|
|
757
|
+
|
|
758
|
+
return None
|
|
759
|
+
|
|
760
|
+
def get_release_id_for_version(self, version_code: str) -> Optional[int]:
|
|
761
|
+
"""
|
|
762
|
+
Get release ID for a version code.
|
|
763
|
+
|
|
764
|
+
Args:
|
|
765
|
+
version_code: Version code (e.g., "4.2")
|
|
766
|
+
|
|
767
|
+
Returns:
|
|
768
|
+
Release ID or None
|
|
769
|
+
"""
|
|
770
|
+
result = (
|
|
771
|
+
self.session.query(Release.releaseid)
|
|
772
|
+
.filter(Release.code == version_code)
|
|
773
|
+
.first()
|
|
774
|
+
)
|
|
775
|
+
|
|
776
|
+
return result[0] if result else None
|
|
777
|
+
|
|
778
|
+
def get_table_list(self, release_id: Optional[int] = None) -> List[str]:
|
|
779
|
+
"""
|
|
780
|
+
Get list of all table names (used by database introspection).
|
|
781
|
+
|
|
782
|
+
Args:
|
|
783
|
+
release_id: Optional release ID to filter by
|
|
784
|
+
|
|
785
|
+
Returns:
|
|
786
|
+
List of table names
|
|
787
|
+
"""
|
|
788
|
+
# For database introspection, we return distinct table codes from datapoints
|
|
789
|
+
return self.get_available_tables_from_datapoints(release_id=release_id)
|
|
790
|
+
|
|
791
|
+
def get_datapoints_count(self, release_id: Optional[int] = None) -> int:
|
|
792
|
+
"""
|
|
793
|
+
Get count of datapoints.
|
|
794
|
+
Always uses ViewDatapoints class methods for database compatibility.
|
|
795
|
+
|
|
796
|
+
Args:
|
|
797
|
+
release_id: Optional release ID to filter by
|
|
798
|
+
|
|
799
|
+
Returns:
|
|
800
|
+
Count of datapoints
|
|
801
|
+
"""
|
|
802
|
+
# Use ViewDatapoints class method (works for both SQLite and PostgreSQL)
|
|
803
|
+
base_query = ViewDatapoints.create_view_query(self.session)
|
|
804
|
+
subq = base_query.subquery()
|
|
805
|
+
|
|
806
|
+
query = self.session.query(func.count(subq.c.cell_code))
|
|
807
|
+
|
|
808
|
+
if release_id is not None:
|
|
809
|
+
query = query.select_from(subq).filter(
|
|
810
|
+
or_(subq.c.end_release.is_(None), subq.c.end_release > release_id),
|
|
811
|
+
subq.c.start_release <= release_id,
|
|
812
|
+
)
|
|
813
|
+
|
|
814
|
+
return query.scalar() or 0
|
|
815
|
+
|
|
816
|
+
# ==================== Module and Table Query Methods ====================
|
|
817
|
+
|
|
818
|
+
def get_all_variables_for_table(self, table_vid: int) -> Dict[str, str]:
|
|
819
|
+
"""
|
|
820
|
+
Get all variables for a table version.
|
|
821
|
+
|
|
822
|
+
Queries SOURCE_DB via TableVersionCell -> VariableVersion -> Property -> DataType
|
|
823
|
+
to get all variable IDs with their single-char type codes.
|
|
824
|
+
|
|
825
|
+
Args:
|
|
826
|
+
table_vid: Table version ID
|
|
827
|
+
|
|
828
|
+
Returns:
|
|
829
|
+
Dictionary mapping variable_id (str) to type_code (str)
|
|
830
|
+
Type codes are single characters from DataType.Code (e.g., 'm', 'e', 'b', 'p')
|
|
831
|
+
"""
|
|
832
|
+
query = (
|
|
833
|
+
self.session.query(Variable.variableid, DataType.code)
|
|
834
|
+
.select_from(TableVersionCell)
|
|
835
|
+
.join(
|
|
836
|
+
VariableVersion,
|
|
837
|
+
TableVersionCell.variablevid == VariableVersion.variablevid,
|
|
838
|
+
)
|
|
839
|
+
.join(Variable, VariableVersion.variableid == Variable.variableid)
|
|
840
|
+
.join(Property, VariableVersion.propertyid == Property.propertyid)
|
|
841
|
+
.join(DataType, Property.datatypeid == DataType.datatypeid)
|
|
842
|
+
.filter(TableVersionCell.tablevid == table_vid)
|
|
843
|
+
.distinct()
|
|
844
|
+
)
|
|
845
|
+
|
|
846
|
+
results = query.all()
|
|
847
|
+
# IMPORTANT: Convert to int first to avoid ".0" suffix from potential float values
|
|
848
|
+
return {str(int(r.variableid)): r.code for r in results if r.code is not None}
|
|
849
|
+
|
|
850
|
+
def get_all_tables_for_module(self, module_vid: int) -> List[Dict[str, Any]]:
|
|
851
|
+
"""
|
|
852
|
+
Get ALL tables belonging to a module version.
|
|
853
|
+
|
|
854
|
+
Queries SOURCE_DB via ModuleVersionComposition to find all tables
|
|
855
|
+
in a module, regardless of whether they're referenced in validations.
|
|
856
|
+
|
|
857
|
+
Args:
|
|
858
|
+
module_vid: Module version ID
|
|
859
|
+
|
|
860
|
+
Returns:
|
|
861
|
+
List of dicts with table_vid, table_code, table_name
|
|
862
|
+
"""
|
|
863
|
+
query = (
|
|
864
|
+
self.session.query(
|
|
865
|
+
TableVersion.tablevid, TableVersion.code, TableVersion.name
|
|
866
|
+
)
|
|
867
|
+
.select_from(ModuleVersionComposition)
|
|
868
|
+
.join(
|
|
869
|
+
TableVersion, ModuleVersionComposition.tablevid == TableVersion.tablevid
|
|
870
|
+
)
|
|
871
|
+
.filter(ModuleVersionComposition.modulevid == module_vid)
|
|
872
|
+
.distinct()
|
|
873
|
+
.order_by(TableVersion.code)
|
|
874
|
+
)
|
|
875
|
+
|
|
876
|
+
results = query.all()
|
|
877
|
+
return [
|
|
878
|
+
{"table_vid": r.tablevid, "table_code": r.code, "table_name": r.name}
|
|
879
|
+
for r in results
|
|
880
|
+
]
|
|
881
|
+
|
|
882
|
+
def __del__(self):
|
|
883
|
+
"""Clean up resources."""
|
|
884
|
+
if hasattr(self, "session") and self.session:
|
|
885
|
+
self.session.close()
|
|
886
|
+
|
|
887
|
+
def close(self):
|
|
888
|
+
"""
|
|
889
|
+
Explicitly close the underlying SQLAlchemy session.
|
|
890
|
+
|
|
891
|
+
This is a no-op if the session is already closed or missing.
|
|
892
|
+
"""
|
|
893
|
+
if hasattr(self, "session") and self.session:
|
|
894
|
+
try:
|
|
895
|
+
self.session.close()
|
|
896
|
+
except Exception:
|
|
897
|
+
pass
|
|
898
|
+
|
|
899
|
+
def __enter__(self):
|
|
900
|
+
return self
|
|
901
|
+
|
|
902
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
903
|
+
self.close()
|