pydpm_xl 0.1.10__py3-none-any.whl → 0.1.39rc24__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/AST/ASTConstructor.py +11 -0
- py_dpm/AST/ASTObjects.py +67 -3
- py_dpm/AST/ASTTemplate.py +5 -1
- py_dpm/AST/MLGeneration.py +12 -0
- py_dpm/AST/check_operands.py +303 -78
- py_dpm/OperationScopes/OperationScopeService.py +94 -30
- py_dpm/Operators/ClauseOperators.py +41 -0
- py_dpm/Operators/ConditionalOperators.py +21 -4
- py_dpm/{utils → Utils}/ast_serialization.py +289 -37
- py_dpm/Utils/operator_mapping.py +3 -2
- py_dpm/Utils/tokens.py +1 -0
- py_dpm/__init__.py +2 -8
- py_dpm/api/__init__.py +73 -4
- py_dpm/api/ast_generator.py +6 -3
- py_dpm/api/complete_ast.py +558 -21
- py_dpm/api/data_dictionary.py +984 -0
- py_dpm/api/data_dictionary_validation.py +44 -7
- py_dpm/api/migration.py +1 -1
- py_dpm/api/operation_scopes.py +1230 -0
- py_dpm/api/semantic.py +173 -65
- py_dpm/client.py +380 -0
- py_dpm/db_utils.py +110 -6
- py_dpm/grammar/dist/dpm_xlLexer.interp +5 -1
- py_dpm/grammar/dist/dpm_xlLexer.py +882 -708
- py_dpm/grammar/dist/dpm_xlLexer.tokens +46 -45
- py_dpm/grammar/dist/dpm_xlParser.interp +3 -1
- py_dpm/grammar/dist/dpm_xlParser.py +762 -614
- py_dpm/grammar/dist/dpm_xlParser.tokens +46 -45
- py_dpm/grammar/dist/dpm_xlParserListener.py +11 -3
- py_dpm/grammar/dist/dpm_xlParserVisitor.py +7 -3
- py_dpm/grammar/dist/listeners.py +1 -1
- py_dpm/grammar/dpm_xlLexer.g4 +3 -1
- py_dpm/grammar/dpm_xlParser.g4 +5 -2
- py_dpm/migration.py +122 -15
- py_dpm/models.py +1718 -502
- py_dpm/semantics/SemanticAnalyzer.py +26 -9
- {pydpm_xl-0.1.10.dist-info → pydpm_xl-0.1.39rc24.dist-info}/METADATA +17 -14
- {pydpm_xl-0.1.10.dist-info → pydpm_xl-0.1.39rc24.dist-info}/RECORD +45 -43
- {pydpm_xl-0.1.10.dist-info → pydpm_xl-0.1.39rc24.dist-info}/WHEEL +2 -1
- pydpm_xl-0.1.39rc24.dist-info/entry_points.txt +2 -0
- pydpm_xl-0.1.39rc24.dist-info/top_level.txt +1 -0
- py_dpm/utils/__init__.py +0 -0
- pydpm_xl-0.1.10.dist-info/entry_points.txt +0 -3
- {pydpm_xl-0.1.10.dist-info → pydpm_xl-0.1.39rc24.dist-info/licenses}/LICENSE +0 -0
py_dpm/AST/ASTConstructor.py
CHANGED
|
@@ -220,6 +220,9 @@ class ASTVisitor(dpm_xlParserVisitor):
|
|
|
220
220
|
elif isinstance(ctx_list[2], dpm_xlParser.RenameExprContext):
|
|
221
221
|
rename_nodes = self.visitRenameExpr(ctx_list[2])
|
|
222
222
|
return RenameOp(operand=operand, rename_nodes=rename_nodes)
|
|
223
|
+
elif isinstance(ctx_list[2], dpm_xlParser.SubExprContext):
|
|
224
|
+
property_code, value = self.visitSubExpr(ctx_list[2])
|
|
225
|
+
return SubOp(operand=operand, property_code=property_code, value=value)
|
|
223
226
|
|
|
224
227
|
def visitWhereExpr(self, ctx: dpm_xlParser.WhereExprContext):
|
|
225
228
|
return self.visit(ctx.getChild(1))
|
|
@@ -241,6 +244,14 @@ class ASTVisitor(dpm_xlParserVisitor):
|
|
|
241
244
|
new_name = self.visit(ctx_list[2])
|
|
242
245
|
return RenameNode(old_name=old_name, new_name=new_name)
|
|
243
246
|
|
|
247
|
+
def visitSubExpr(self, ctx: dpm_xlParser.SubExprContext):
|
|
248
|
+
# SUB propertyCode EQ (literal | select | itemReference)
|
|
249
|
+
ctx_list = list(ctx.getChildren())
|
|
250
|
+
property_code = self.visit(ctx_list[1]) # propertyCode
|
|
251
|
+
# ctx_list[2] is EQ
|
|
252
|
+
value = self.visit(ctx_list[3]) # literal, select, or itemReference
|
|
253
|
+
return property_code, value
|
|
254
|
+
|
|
244
255
|
def create_bin_op(self, ctx: dpm_xlParser.ExpressionContext):
|
|
245
256
|
ctx_list = list(ctx.getChildren())
|
|
246
257
|
|
py_dpm/AST/ASTObjects.py
CHANGED
|
@@ -234,9 +234,11 @@ class VarID(AST):
|
|
|
234
234
|
# If data has been populated (after operand checking), use that
|
|
235
235
|
if hasattr(self, 'data') and self.data is not None:
|
|
236
236
|
# Convert DataFrame to list of dictionaries
|
|
237
|
+
data_records = None
|
|
237
238
|
try:
|
|
238
239
|
if hasattr(self.data, 'to_dict'):
|
|
239
|
-
|
|
240
|
+
data_records = self.data.to_dict('records')
|
|
241
|
+
result['data'] = data_records
|
|
240
242
|
else:
|
|
241
243
|
result['data'] = self.data
|
|
242
244
|
except Exception:
|
|
@@ -246,15 +248,43 @@ class VarID(AST):
|
|
|
246
248
|
except Exception:
|
|
247
249
|
result['data'] = str(self.data)
|
|
248
250
|
result['table'] = self.table
|
|
249
|
-
|
|
251
|
+
|
|
252
|
+
# Interval handling: determine from data_type in data records
|
|
253
|
+
# According to DPM-XL spec, interval only applies to Number types
|
|
254
|
+
interval_value = False # default
|
|
255
|
+
|
|
256
|
+
# First check if type attribute is set (from semantic validation)
|
|
257
|
+
if hasattr(self, 'type') and self.type is not None:
|
|
258
|
+
from py_dpm.DataTypes.ScalarTypes import Number
|
|
259
|
+
if isinstance(self.type, Number):
|
|
260
|
+
interval_value = self.interval if self.interval is not None else False
|
|
261
|
+
else:
|
|
262
|
+
interval_value = None
|
|
263
|
+
# Otherwise, infer from data_type in data records
|
|
264
|
+
elif data_records and len(data_records) > 0 and 'data_type' in data_records[0]:
|
|
265
|
+
data_type = data_records[0]['data_type']
|
|
266
|
+
# Map database data types to determine if numeric
|
|
267
|
+
# Numeric types: 'i' (integer), 'r' (decimal), 'm' (monetary), 'p' (percentage)
|
|
268
|
+
# Non-numeric types: 'b' (boolean), 's' (string), 'e' (enumeration/item), etc.
|
|
269
|
+
numeric_data_types = {'i', 'r', 'm', 'p', 'INT', 'DEC', 'MON', 'PER'}
|
|
270
|
+
if data_type in numeric_data_types:
|
|
271
|
+
interval_value = self.interval if self.interval is not None else False
|
|
272
|
+
else:
|
|
273
|
+
interval_value = None
|
|
274
|
+
else:
|
|
275
|
+
# No type info available - use explicit interval or default to False
|
|
276
|
+
interval_value = self.interval if self.interval is not None else False
|
|
277
|
+
|
|
278
|
+
result['interval'] = interval_value
|
|
250
279
|
else:
|
|
251
280
|
# Use original structure for unexpanded VarID
|
|
281
|
+
# When there's no data (no semantic validation), default interval to False
|
|
252
282
|
result.update({
|
|
253
283
|
'table': self.table,
|
|
254
284
|
'rows': self.rows,
|
|
255
285
|
'cols': self.cols,
|
|
256
286
|
'sheets': self.sheets,
|
|
257
|
-
'interval': self.interval,
|
|
287
|
+
'interval': self.interval if self.interval is not None else False,
|
|
258
288
|
'default': self.default,
|
|
259
289
|
'is_table_group': self.is_table_group
|
|
260
290
|
})
|
|
@@ -710,6 +740,40 @@ class RenameNode(AST):
|
|
|
710
740
|
}
|
|
711
741
|
|
|
712
742
|
|
|
743
|
+
class SubOp(AST):
|
|
744
|
+
"""
|
|
745
|
+
AST Object for the Sub operator. Filters a recordset based on a property substitution.
|
|
746
|
+
|
|
747
|
+
:parameter operand: Recordset to be filtered
|
|
748
|
+
:parameter property_code: Property code to substitute
|
|
749
|
+
:parameter value: Value to substitute (can be a literal, select, or itemReference)
|
|
750
|
+
"""
|
|
751
|
+
|
|
752
|
+
def __init__(self, operand, property_code, value):
|
|
753
|
+
super().__init__()
|
|
754
|
+
self.operand = operand
|
|
755
|
+
self.property_code = property_code
|
|
756
|
+
self.value = value
|
|
757
|
+
|
|
758
|
+
def __str__(self):
|
|
759
|
+
return "<AST(name='{name}', operand={operand}, property_code='{property_code}', value={value})>".format(
|
|
760
|
+
name=self.__class__.__name__,
|
|
761
|
+
operand=self.operand,
|
|
762
|
+
property_code=self.property_code,
|
|
763
|
+
value=self.value
|
|
764
|
+
)
|
|
765
|
+
|
|
766
|
+
__repr__ = __str__
|
|
767
|
+
|
|
768
|
+
def toJSON(self):
|
|
769
|
+
return {
|
|
770
|
+
'class_name': self.__class__.__name__,
|
|
771
|
+
'operand': self.operand,
|
|
772
|
+
'property_code': self.property_code,
|
|
773
|
+
'value': self.value
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
|
|
713
777
|
class PropertyReference(AST):
|
|
714
778
|
|
|
715
779
|
def __init__(self, code):
|
py_dpm/AST/ASTTemplate.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from py_dpm.AST.ASTObjects import AggregationOp, BinOp, ComplexNumericOp, CondExpr, Constant, Dimension, FilterOp, GetOp, GroupingClause, \
|
|
2
|
-
OperationRef, ParExpr, PersistentAssignment, PreconditionItem, PropertyReference, RenameOp, Scalar, Set, Start, TemporaryAssignment, \
|
|
2
|
+
OperationRef, ParExpr, PersistentAssignment, PreconditionItem, PropertyReference, RenameOp, Scalar, Set, Start, SubOp, TemporaryAssignment, \
|
|
3
3
|
TimeShiftOp, UnaryOp, VarID, VarRef, WhereClauseOp, WithExpression
|
|
4
4
|
from py_dpm.AST.ASTVisitor import NodeVisitor
|
|
5
5
|
|
|
@@ -84,6 +84,10 @@ class ASTTemplate(NodeVisitor):
|
|
|
84
84
|
def visit_GetOp(self, node: GetOp):
|
|
85
85
|
self.visit(node.operand)
|
|
86
86
|
|
|
87
|
+
def visit_SubOp(self, node: SubOp):
|
|
88
|
+
self.visit(node.operand)
|
|
89
|
+
self.visit(node.value)
|
|
90
|
+
|
|
87
91
|
def visit_PreconditionItem(self, node: PreconditionItem):
|
|
88
92
|
pass
|
|
89
93
|
|
py_dpm/AST/MLGeneration.py
CHANGED
|
@@ -400,6 +400,18 @@ class MLGeneration(ASTTemplate):
|
|
|
400
400
|
)
|
|
401
401
|
self.session.add(new_operand_ref)
|
|
402
402
|
|
|
403
|
+
def visit_SubOp(self, node: SubOp):
|
|
404
|
+
setattr(node, "op", "sub")
|
|
405
|
+
operand_node = self.create_operation_node(node)
|
|
406
|
+
setattr(node.operand, "parent", operand_node)
|
|
407
|
+
setattr(node.operand, "argument", "operand")
|
|
408
|
+
self.visit(node.operand)
|
|
409
|
+
|
|
410
|
+
# Visit the value (can be literal, select, or itemReference)
|
|
411
|
+
setattr(node.value, "parent", operand_node)
|
|
412
|
+
setattr(node.value, "argument", "value")
|
|
413
|
+
self.visit(node.value)
|
|
414
|
+
|
|
403
415
|
def visit_PreconditionItem(self, node: PreconditionItem):
|
|
404
416
|
operand_node = self.create_operation_node(node, is_leaf=True)
|
|
405
417
|
operand_reference = "PreconditionItem" # "$_{}".format(node.value)
|