vtlengine 1.4.0rc2__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.
- vtlengine/API/_InternalApi.py +791 -0
- vtlengine/API/__init__.py +612 -0
- vtlengine/API/data/schema/external_routines_schema.json +34 -0
- vtlengine/API/data/schema/json_schema_2.1.json +116 -0
- vtlengine/API/data/schema/value_domain_schema.json +97 -0
- vtlengine/AST/ASTComment.py +57 -0
- vtlengine/AST/ASTConstructor.py +598 -0
- vtlengine/AST/ASTConstructorModules/Expr.py +1928 -0
- vtlengine/AST/ASTConstructorModules/ExprComponents.py +995 -0
- vtlengine/AST/ASTConstructorModules/Terminals.py +790 -0
- vtlengine/AST/ASTConstructorModules/__init__.py +50 -0
- vtlengine/AST/ASTDataExchange.py +10 -0
- vtlengine/AST/ASTEncoders.py +32 -0
- vtlengine/AST/ASTString.py +675 -0
- vtlengine/AST/ASTTemplate.py +558 -0
- vtlengine/AST/ASTVisitor.py +25 -0
- vtlengine/AST/DAG/__init__.py +479 -0
- vtlengine/AST/DAG/_words.py +10 -0
- vtlengine/AST/Grammar/Vtl.g4 +705 -0
- vtlengine/AST/Grammar/VtlTokens.g4 +409 -0
- vtlengine/AST/Grammar/__init__.py +0 -0
- vtlengine/AST/Grammar/lexer.py +2139 -0
- vtlengine/AST/Grammar/parser.py +16597 -0
- vtlengine/AST/Grammar/tokens.py +169 -0
- vtlengine/AST/VtlVisitor.py +824 -0
- vtlengine/AST/__init__.py +674 -0
- vtlengine/DataTypes/TimeHandling.py +562 -0
- vtlengine/DataTypes/__init__.py +863 -0
- vtlengine/DataTypes/_time_checking.py +135 -0
- vtlengine/Exceptions/__exception_file_generator.py +96 -0
- vtlengine/Exceptions/__init__.py +159 -0
- vtlengine/Exceptions/messages.py +1004 -0
- vtlengine/Interpreter/__init__.py +2048 -0
- vtlengine/Model/__init__.py +501 -0
- vtlengine/Operators/Aggregation.py +357 -0
- vtlengine/Operators/Analytic.py +455 -0
- vtlengine/Operators/Assignment.py +23 -0
- vtlengine/Operators/Boolean.py +106 -0
- vtlengine/Operators/CastOperator.py +451 -0
- vtlengine/Operators/Clause.py +366 -0
- vtlengine/Operators/Comparison.py +488 -0
- vtlengine/Operators/Conditional.py +495 -0
- vtlengine/Operators/General.py +191 -0
- vtlengine/Operators/HROperators.py +254 -0
- vtlengine/Operators/Join.py +447 -0
- vtlengine/Operators/Numeric.py +422 -0
- vtlengine/Operators/RoleSetter.py +77 -0
- vtlengine/Operators/Set.py +176 -0
- vtlengine/Operators/String.py +578 -0
- vtlengine/Operators/Time.py +1144 -0
- vtlengine/Operators/Validation.py +275 -0
- vtlengine/Operators/__init__.py +900 -0
- vtlengine/Utils/__Virtual_Assets.py +34 -0
- vtlengine/Utils/__init__.py +479 -0
- vtlengine/__extras_check.py +17 -0
- vtlengine/__init__.py +27 -0
- vtlengine/files/__init__.py +0 -0
- vtlengine/files/output/__init__.py +35 -0
- vtlengine/files/output/_time_period_representation.py +55 -0
- vtlengine/files/parser/__init__.py +240 -0
- vtlengine/files/parser/_rfc_dialect.py +22 -0
- vtlengine/py.typed +0 -0
- vtlengine-1.4.0rc2.dist-info/METADATA +89 -0
- vtlengine-1.4.0rc2.dist-info/RECORD +66 -0
- vtlengine-1.4.0rc2.dist-info/WHEEL +4 -0
- vtlengine-1.4.0rc2.dist-info/licenses/LICENSE.md +661 -0
|
@@ -0,0 +1,863 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Any, Dict, Optional, Set, Type, Union
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
|
|
6
|
+
from vtlengine.DataTypes._time_checking import (
|
|
7
|
+
check_date,
|
|
8
|
+
check_time,
|
|
9
|
+
check_time_period,
|
|
10
|
+
)
|
|
11
|
+
from vtlengine.DataTypes.TimeHandling import (
|
|
12
|
+
check_max_date,
|
|
13
|
+
date_to_period_str,
|
|
14
|
+
str_period_to_date,
|
|
15
|
+
)
|
|
16
|
+
from vtlengine.Exceptions import RunTimeError, SemanticError
|
|
17
|
+
|
|
18
|
+
DTYPE_MAPPING: Dict[str, str] = {
|
|
19
|
+
"String": "string",
|
|
20
|
+
"Number": "float64",
|
|
21
|
+
"Integer": "int64",
|
|
22
|
+
"TimeInterval": "string",
|
|
23
|
+
"Date": "string",
|
|
24
|
+
"TimePeriod": "string",
|
|
25
|
+
"Duration": "string",
|
|
26
|
+
"Boolean": "object",
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
CAST_MAPPING: Dict[str, type] = {
|
|
30
|
+
"String": str,
|
|
31
|
+
"Number": float,
|
|
32
|
+
"Integer": int,
|
|
33
|
+
"TimeInterval": str,
|
|
34
|
+
"Date": str,
|
|
35
|
+
"TimePeriod": str,
|
|
36
|
+
"Duration": str,
|
|
37
|
+
"Boolean": bool,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class DataTypeSimpleRepr(type):
|
|
42
|
+
def __repr__(cls) -> Any:
|
|
43
|
+
return SCALAR_TYPES_CLASS_REVERSE[cls]
|
|
44
|
+
|
|
45
|
+
def __hash__(cls) -> int:
|
|
46
|
+
return id(cls)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class ScalarType(metaclass=DataTypeSimpleRepr):
|
|
50
|
+
""" """
|
|
51
|
+
|
|
52
|
+
default: Optional[Union[str, "ScalarType"]] = None
|
|
53
|
+
|
|
54
|
+
def strictly_same_class(self, obj: "ScalarType") -> bool:
|
|
55
|
+
if not isinstance(obj, ScalarType):
|
|
56
|
+
raise Exception("Not use strictly_same_class")
|
|
57
|
+
return self.__class__ == obj.__class__
|
|
58
|
+
|
|
59
|
+
def __eq__(self, other: Any) -> bool:
|
|
60
|
+
return self.__class__.__name__ == other.__class__.__name__
|
|
61
|
+
|
|
62
|
+
def __ne__(self, other: Any) -> bool:
|
|
63
|
+
return not self.__eq__(other)
|
|
64
|
+
|
|
65
|
+
def instance_is_included(self, set_: Set[Any]) -> bool:
|
|
66
|
+
return self.__class__ in set_
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def is_included(cls, set_: Set[Any]) -> bool:
|
|
70
|
+
return cls in set_
|
|
71
|
+
|
|
72
|
+
@classmethod
|
|
73
|
+
def promotion_changed_type(cls, promoted: Any) -> bool:
|
|
74
|
+
return not issubclass(cls, promoted)
|
|
75
|
+
|
|
76
|
+
@classmethod
|
|
77
|
+
def implicit_cast(cls, value: Any, from_type: Any) -> Any:
|
|
78
|
+
raise Exception("Method should be implemented by inheritors")
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def explicit_cast(cls, value: Any, from_type: Any) -> Any:
|
|
82
|
+
raise Exception("Method should be implemented by inheritors")
|
|
83
|
+
|
|
84
|
+
@classmethod
|
|
85
|
+
def is_subtype(cls, obj: Any) -> bool:
|
|
86
|
+
return issubclass(cls, obj)
|
|
87
|
+
|
|
88
|
+
@classmethod
|
|
89
|
+
def is_null_type(cls) -> bool:
|
|
90
|
+
return False
|
|
91
|
+
|
|
92
|
+
@classmethod
|
|
93
|
+
def check_type(cls, value: Any) -> bool:
|
|
94
|
+
if isinstance(value, CAST_MAPPING[cls.__name__]):
|
|
95
|
+
return True
|
|
96
|
+
raise Exception(f"Value {value} is not a {cls.__name__}")
|
|
97
|
+
|
|
98
|
+
@classmethod
|
|
99
|
+
def cast(cls, value: Any) -> Any:
|
|
100
|
+
if pd.isnull(value):
|
|
101
|
+
return None
|
|
102
|
+
class_name: str = cls.__name__.__str__()
|
|
103
|
+
return CAST_MAPPING[class_name](value)
|
|
104
|
+
|
|
105
|
+
@classmethod
|
|
106
|
+
def dtype(cls) -> str:
|
|
107
|
+
class_name: str = cls.__name__.__str__()
|
|
108
|
+
return DTYPE_MAPPING[class_name]
|
|
109
|
+
|
|
110
|
+
@classmethod
|
|
111
|
+
def check(cls, value: Any) -> bool:
|
|
112
|
+
try:
|
|
113
|
+
cls.cast(value)
|
|
114
|
+
return True
|
|
115
|
+
except Exception:
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class String(ScalarType):
|
|
120
|
+
""" """
|
|
121
|
+
|
|
122
|
+
default = ""
|
|
123
|
+
|
|
124
|
+
@classmethod
|
|
125
|
+
def implicit_cast(cls, value: Any, from_type: Any) -> str:
|
|
126
|
+
# if pd.isna(value):
|
|
127
|
+
# return cls.default
|
|
128
|
+
if from_type in {
|
|
129
|
+
Number,
|
|
130
|
+
Integer,
|
|
131
|
+
Boolean,
|
|
132
|
+
String,
|
|
133
|
+
Date,
|
|
134
|
+
TimePeriod,
|
|
135
|
+
TimeInterval,
|
|
136
|
+
Duration,
|
|
137
|
+
}:
|
|
138
|
+
return str(value)
|
|
139
|
+
|
|
140
|
+
raise SemanticError(
|
|
141
|
+
"2-1-5-1",
|
|
142
|
+
value=value,
|
|
143
|
+
type_1=SCALAR_TYPES_CLASS_REVERSE[from_type],
|
|
144
|
+
type_2=SCALAR_TYPES_CLASS_REVERSE[cls],
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
@classmethod
|
|
148
|
+
def explicit_cast(cls, value: Any, from_type: Any) -> str:
|
|
149
|
+
if from_type in {TimePeriod, Date, String}:
|
|
150
|
+
return str(value)
|
|
151
|
+
|
|
152
|
+
raise RunTimeError(
|
|
153
|
+
"2-1-5-1",
|
|
154
|
+
value=value,
|
|
155
|
+
type_1=SCALAR_TYPES_CLASS_REVERSE[from_type],
|
|
156
|
+
type_2=SCALAR_TYPES_CLASS_REVERSE[cls],
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
@classmethod
|
|
160
|
+
def check(cls, value: Any) -> bool:
|
|
161
|
+
return True
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class Number(ScalarType):
|
|
165
|
+
""" """
|
|
166
|
+
|
|
167
|
+
def __eq__(self, other: Any) -> bool:
|
|
168
|
+
return (
|
|
169
|
+
self.__class__.__name__ == other.__class__.__name__
|
|
170
|
+
or other.__class__.__name__ == Integer.__name__
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
def __ne__(self, other: Any) -> bool:
|
|
174
|
+
return not self.__eq__(other)
|
|
175
|
+
|
|
176
|
+
@classmethod
|
|
177
|
+
def implicit_cast(cls, value: Any, from_type: Any) -> float:
|
|
178
|
+
# if pd.isna(value):
|
|
179
|
+
# return cls.default
|
|
180
|
+
if from_type in {Integer, Number}:
|
|
181
|
+
return float(value)
|
|
182
|
+
|
|
183
|
+
raise RunTimeError(
|
|
184
|
+
"2-1-5-1",
|
|
185
|
+
value=value,
|
|
186
|
+
type_1=SCALAR_TYPES_CLASS_REVERSE[from_type],
|
|
187
|
+
type_2=SCALAR_TYPES_CLASS_REVERSE[cls],
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
@classmethod
|
|
191
|
+
def explicit_cast(cls, value: Any, from_type: Any) -> float:
|
|
192
|
+
if from_type in {Boolean}:
|
|
193
|
+
if value:
|
|
194
|
+
return 1.0
|
|
195
|
+
else:
|
|
196
|
+
return 0.0
|
|
197
|
+
elif from_type in {Integer, Number, String}:
|
|
198
|
+
try:
|
|
199
|
+
return float(value)
|
|
200
|
+
except ValueError:
|
|
201
|
+
pass
|
|
202
|
+
|
|
203
|
+
raise RunTimeError(
|
|
204
|
+
"2-1-5-1",
|
|
205
|
+
value=value,
|
|
206
|
+
type_1=SCALAR_TYPES_CLASS_REVERSE[from_type],
|
|
207
|
+
type_2=SCALAR_TYPES_CLASS_REVERSE[cls],
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
@classmethod
|
|
211
|
+
def cast(cls, value: Any) -> Optional[float]:
|
|
212
|
+
if pd.isnull(value):
|
|
213
|
+
return None
|
|
214
|
+
if isinstance(value, str):
|
|
215
|
+
if value.lower() == "true":
|
|
216
|
+
return 1.0
|
|
217
|
+
elif value.lower() == "false":
|
|
218
|
+
return 0.0
|
|
219
|
+
return float(value)
|
|
220
|
+
|
|
221
|
+
@classmethod
|
|
222
|
+
def check(cls, value: Any) -> bool:
|
|
223
|
+
if pd.isnull(value):
|
|
224
|
+
return True
|
|
225
|
+
if isinstance(value, (int, float, bool)):
|
|
226
|
+
return True
|
|
227
|
+
if isinstance(value, str):
|
|
228
|
+
v = value.strip()
|
|
229
|
+
if v.lower() in {"true", "false"}:
|
|
230
|
+
return True
|
|
231
|
+
return bool(re.match(r"^\d+(\.\d*)?$|^\.\d+$", v))
|
|
232
|
+
return False
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class Integer(Number):
|
|
236
|
+
""" """
|
|
237
|
+
|
|
238
|
+
def __eq__(self, other: Any) -> bool:
|
|
239
|
+
return (
|
|
240
|
+
self.__class__.__name__ == other.__class__.__name__
|
|
241
|
+
or other.__class__.__name__ == Number.__name__
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
def __ne__(self, other: Any) -> bool:
|
|
245
|
+
return not self.__eq__(other)
|
|
246
|
+
|
|
247
|
+
@classmethod
|
|
248
|
+
def implicit_cast(cls, value: Any, from_type: Any) -> int:
|
|
249
|
+
if from_type.__name__ == "Integer":
|
|
250
|
+
return value
|
|
251
|
+
|
|
252
|
+
if from_type.__name__ == "Number":
|
|
253
|
+
if value.is_integer():
|
|
254
|
+
return int(value)
|
|
255
|
+
else:
|
|
256
|
+
raise RunTimeError(
|
|
257
|
+
"2-1-5-1",
|
|
258
|
+
value=value,
|
|
259
|
+
type_1=SCALAR_TYPES_CLASS_REVERSE[from_type],
|
|
260
|
+
type_2=SCALAR_TYPES_CLASS_REVERSE[cls],
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
raise RunTimeError(
|
|
264
|
+
"2-1-5-1",
|
|
265
|
+
value=value,
|
|
266
|
+
type_1=SCALAR_TYPES_CLASS_REVERSE[from_type],
|
|
267
|
+
type_2=SCALAR_TYPES_CLASS_REVERSE[cls],
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
@classmethod
|
|
271
|
+
def explicit_cast(cls, value: Any, from_type: Any) -> int:
|
|
272
|
+
if from_type in {Boolean}:
|
|
273
|
+
if value:
|
|
274
|
+
return 1
|
|
275
|
+
else:
|
|
276
|
+
return 0
|
|
277
|
+
if from_type in {Number, String}:
|
|
278
|
+
try:
|
|
279
|
+
if float(value) - int(value) != 0:
|
|
280
|
+
raise RunTimeError(
|
|
281
|
+
"2-1-5-1",
|
|
282
|
+
value=value,
|
|
283
|
+
type_1=SCALAR_TYPES_CLASS_REVERSE[from_type],
|
|
284
|
+
type_2=SCALAR_TYPES_CLASS_REVERSE[cls],
|
|
285
|
+
)
|
|
286
|
+
except ValueError:
|
|
287
|
+
raise RunTimeError(
|
|
288
|
+
"2-1-5-1",
|
|
289
|
+
value=value,
|
|
290
|
+
type_1=SCALAR_TYPES_CLASS_REVERSE[from_type],
|
|
291
|
+
type_2=SCALAR_TYPES_CLASS_REVERSE[cls],
|
|
292
|
+
)
|
|
293
|
+
return int(value)
|
|
294
|
+
|
|
295
|
+
raise RunTimeError(
|
|
296
|
+
"2-1-5-1",
|
|
297
|
+
value=value,
|
|
298
|
+
type_1=SCALAR_TYPES_CLASS_REVERSE[from_type],
|
|
299
|
+
type_2=SCALAR_TYPES_CLASS_REVERSE[cls],
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
@classmethod
|
|
303
|
+
def cast(cls, value: Any) -> Optional[int]:
|
|
304
|
+
if pd.isnull(value):
|
|
305
|
+
return None
|
|
306
|
+
if isinstance(value, float):
|
|
307
|
+
# Check if the float has decimals
|
|
308
|
+
if value.is_integer():
|
|
309
|
+
return int(value)
|
|
310
|
+
else:
|
|
311
|
+
raise ValueError(f"Value {value} has decimals, cannot cast to integer")
|
|
312
|
+
if isinstance(value, str):
|
|
313
|
+
if value.lower() == "true":
|
|
314
|
+
return 1
|
|
315
|
+
elif value.lower() == "false":
|
|
316
|
+
return 0
|
|
317
|
+
return int(value)
|
|
318
|
+
|
|
319
|
+
@classmethod
|
|
320
|
+
def check(cls, value: Any) -> bool:
|
|
321
|
+
if pd.isnull(value):
|
|
322
|
+
return True
|
|
323
|
+
if isinstance(value, str):
|
|
324
|
+
return value.isdigit() or value.lower() in {"true", "false"}
|
|
325
|
+
if isinstance(value, float):
|
|
326
|
+
return value.is_integer()
|
|
327
|
+
return isinstance(value, (int, bool))
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
class TimeInterval(ScalarType):
|
|
331
|
+
""" """
|
|
332
|
+
|
|
333
|
+
default = None
|
|
334
|
+
|
|
335
|
+
@classmethod
|
|
336
|
+
def implicit_cast(cls, value: Any, from_type: Any) -> Any:
|
|
337
|
+
# TODO: Remove String, only for compatibility with previous engine
|
|
338
|
+
if from_type in {TimeInterval, String}:
|
|
339
|
+
return value
|
|
340
|
+
if from_type in {Date}:
|
|
341
|
+
value = check_max_date(value)
|
|
342
|
+
|
|
343
|
+
return f"{value}/{value}"
|
|
344
|
+
|
|
345
|
+
if from_type in {TimePeriod}:
|
|
346
|
+
init_value = str_period_to_date(value, start=True).isoformat()
|
|
347
|
+
end_value = str_period_to_date(value, start=False).isoformat()
|
|
348
|
+
return f"{init_value}/{end_value}"
|
|
349
|
+
|
|
350
|
+
raise RunTimeError(
|
|
351
|
+
"2-1-5-1",
|
|
352
|
+
value=value,
|
|
353
|
+
type_1=SCALAR_TYPES_CLASS_REVERSE[from_type],
|
|
354
|
+
type_2=SCALAR_TYPES_CLASS_REVERSE[cls],
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
@classmethod
|
|
358
|
+
def explicit_cast(cls, value: Any, from_type: Any) -> Any:
|
|
359
|
+
if from_type == String:
|
|
360
|
+
return value # check_time(value). TODO: resolve this to avoid a circular import.
|
|
361
|
+
raise RunTimeError(
|
|
362
|
+
"2-1-5-1",
|
|
363
|
+
value=value,
|
|
364
|
+
type_1=SCALAR_TYPES_CLASS_REVERSE[from_type],
|
|
365
|
+
type_2=SCALAR_TYPES_CLASS_REVERSE[cls],
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
@classmethod
|
|
369
|
+
def check(cls, value: Any) -> bool:
|
|
370
|
+
if pd.isnull(value):
|
|
371
|
+
return True
|
|
372
|
+
try:
|
|
373
|
+
check_time(value)
|
|
374
|
+
except Exception:
|
|
375
|
+
return False
|
|
376
|
+
return True
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
class Date(TimeInterval):
|
|
380
|
+
""" """
|
|
381
|
+
|
|
382
|
+
default = None
|
|
383
|
+
|
|
384
|
+
@classmethod
|
|
385
|
+
def implicit_cast(cls, value: Any, from_type: Any) -> Any:
|
|
386
|
+
# TODO: Remove String, only for compatibility with previous engine
|
|
387
|
+
if from_type in {Date, String}:
|
|
388
|
+
return check_max_date(value)
|
|
389
|
+
|
|
390
|
+
raise RunTimeError(
|
|
391
|
+
"2-1-5-1",
|
|
392
|
+
value=value,
|
|
393
|
+
type_1=SCALAR_TYPES_CLASS_REVERSE[from_type],
|
|
394
|
+
type_2=SCALAR_TYPES_CLASS_REVERSE[cls],
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
@classmethod
|
|
398
|
+
def explicit_cast(cls, value: Any, from_type: Any) -> Any:
|
|
399
|
+
# TODO: Remove String, only for compatibility with previous engine
|
|
400
|
+
if from_type == String:
|
|
401
|
+
return check_max_date(value)
|
|
402
|
+
|
|
403
|
+
raise RunTimeError(
|
|
404
|
+
"2-1-5-1",
|
|
405
|
+
value=value,
|
|
406
|
+
type_1=SCALAR_TYPES_CLASS_REVERSE[from_type],
|
|
407
|
+
type_2=SCALAR_TYPES_CLASS_REVERSE[cls],
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
@classmethod
|
|
411
|
+
def check(cls, value: Any) -> bool:
|
|
412
|
+
if pd.isnull(value):
|
|
413
|
+
return True
|
|
414
|
+
try:
|
|
415
|
+
check_date(value)
|
|
416
|
+
except Exception:
|
|
417
|
+
return False
|
|
418
|
+
return True
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
class TimePeriod(TimeInterval):
|
|
422
|
+
""" """
|
|
423
|
+
|
|
424
|
+
default = None
|
|
425
|
+
|
|
426
|
+
@classmethod
|
|
427
|
+
def implicit_cast(cls, value: Any, from_type: Any) -> Any:
|
|
428
|
+
# TODO: Remove String, only for compatibility with previous engine
|
|
429
|
+
if from_type in {TimePeriod, String}:
|
|
430
|
+
return value
|
|
431
|
+
|
|
432
|
+
raise RunTimeError(
|
|
433
|
+
"2-1-5-1",
|
|
434
|
+
value=value,
|
|
435
|
+
type_1=SCALAR_TYPES_CLASS_REVERSE[from_type],
|
|
436
|
+
type_2=SCALAR_TYPES_CLASS_REVERSE[cls],
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
@classmethod
|
|
440
|
+
def explicit_cast(cls, value: Any, from_type: Any) -> Any:
|
|
441
|
+
if from_type in {Date}:
|
|
442
|
+
try:
|
|
443
|
+
period_str = date_to_period_str(value, "D")
|
|
444
|
+
except ValueError:
|
|
445
|
+
raise RunTimeError(
|
|
446
|
+
"2-1-5-1",
|
|
447
|
+
value=value,
|
|
448
|
+
type_1=SCALAR_TYPES_CLASS_REVERSE[from_type],
|
|
449
|
+
type_2=SCALAR_TYPES_CLASS_REVERSE[cls],
|
|
450
|
+
)
|
|
451
|
+
return period_str
|
|
452
|
+
# TODO: Remove String, only for compatibility with previous engine
|
|
453
|
+
elif from_type == String:
|
|
454
|
+
return value # check_time_period(value) TODO: resolve this to avoid a circular import.
|
|
455
|
+
|
|
456
|
+
raise RunTimeError(
|
|
457
|
+
"2-1-5-1",
|
|
458
|
+
value=value,
|
|
459
|
+
type_1=SCALAR_TYPES_CLASS_REVERSE[from_type],
|
|
460
|
+
type_2=SCALAR_TYPES_CLASS_REVERSE[cls],
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
@classmethod
|
|
464
|
+
def check(cls, value: Any) -> bool:
|
|
465
|
+
if pd.isnull(value):
|
|
466
|
+
return True
|
|
467
|
+
try:
|
|
468
|
+
check_time_period(value)
|
|
469
|
+
except Exception:
|
|
470
|
+
return False
|
|
471
|
+
return True
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
class Duration(ScalarType):
|
|
475
|
+
iso8601_duration_pattern = r"^P((\d+Y)?(\d+M)?(\d+D)?)$"
|
|
476
|
+
|
|
477
|
+
@classmethod
|
|
478
|
+
def validate_duration(cls, value: Any) -> bool:
|
|
479
|
+
try:
|
|
480
|
+
match = re.match(cls.iso8601_duration_pattern, value)
|
|
481
|
+
return bool(match)
|
|
482
|
+
except Exception:
|
|
483
|
+
raise RunTimeError(
|
|
484
|
+
"2-1-5-1", value=value, type_1=type(value).__name__, type_2="Duration"
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
@classmethod
|
|
488
|
+
def implicit_cast(cls, value: Any, from_type: Any) -> str:
|
|
489
|
+
if from_type == String and cls.validate_duration(value):
|
|
490
|
+
return value
|
|
491
|
+
|
|
492
|
+
raise RunTimeError(
|
|
493
|
+
"2-1-5-1",
|
|
494
|
+
value=value,
|
|
495
|
+
type_1=SCALAR_TYPES_CLASS_REVERSE[from_type],
|
|
496
|
+
type_2=SCALAR_TYPES_CLASS_REVERSE[cls],
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
@classmethod
|
|
500
|
+
def explicit_cast(cls, value: Any, from_type: Any) -> Any:
|
|
501
|
+
if from_type == String and cls.validate_duration(value):
|
|
502
|
+
return value
|
|
503
|
+
|
|
504
|
+
raise RunTimeError(
|
|
505
|
+
"2-1-5-1",
|
|
506
|
+
value=value,
|
|
507
|
+
type_1=SCALAR_TYPES_CLASS_REVERSE[from_type],
|
|
508
|
+
type_2=SCALAR_TYPES_CLASS_REVERSE[cls],
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
@classmethod
|
|
512
|
+
def to_days(cls, value: Any) -> int:
|
|
513
|
+
if not cls.validate_duration(value):
|
|
514
|
+
raise RunTimeError("2-1-19-15", op=value)
|
|
515
|
+
|
|
516
|
+
match = re.match(cls.iso8601_duration_pattern, value)
|
|
517
|
+
|
|
518
|
+
years = 0
|
|
519
|
+
months = 0
|
|
520
|
+
days = 0
|
|
521
|
+
|
|
522
|
+
years_str = match.group(2) # type: ignore[union-attr]
|
|
523
|
+
months_str = match.group(3) # type: ignore[union-attr]
|
|
524
|
+
days_str = match.group(4) # type: ignore[union-attr]
|
|
525
|
+
if years_str:
|
|
526
|
+
years = int(years_str[:-1])
|
|
527
|
+
if months_str:
|
|
528
|
+
months = int(months_str[:-1])
|
|
529
|
+
if days_str:
|
|
530
|
+
days = int(days_str[:-1])
|
|
531
|
+
total_days = years * 365 + months * 30 + days
|
|
532
|
+
return int(total_days)
|
|
533
|
+
|
|
534
|
+
@classmethod
|
|
535
|
+
def check(cls, value: Any) -> bool:
|
|
536
|
+
if pd.isnull(value):
|
|
537
|
+
return True
|
|
538
|
+
|
|
539
|
+
if isinstance(value, str):
|
|
540
|
+
match = re.match(cls.iso8601_duration_pattern, value)
|
|
541
|
+
return bool(match)
|
|
542
|
+
return False
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
class Boolean(ScalarType):
|
|
546
|
+
""" """
|
|
547
|
+
|
|
548
|
+
default = None
|
|
549
|
+
|
|
550
|
+
@classmethod
|
|
551
|
+
def cast(cls, value: Any) -> Optional[bool]:
|
|
552
|
+
if pd.isnull(value):
|
|
553
|
+
return None
|
|
554
|
+
if isinstance(value, str):
|
|
555
|
+
if value.lower() == "true":
|
|
556
|
+
return True
|
|
557
|
+
elif value.lower() == "false":
|
|
558
|
+
return False
|
|
559
|
+
elif value.lower() == "1":
|
|
560
|
+
return True
|
|
561
|
+
elif value.lower() == "0":
|
|
562
|
+
return False
|
|
563
|
+
else:
|
|
564
|
+
return None
|
|
565
|
+
if isinstance(value, int):
|
|
566
|
+
return value != 0
|
|
567
|
+
if isinstance(value, float):
|
|
568
|
+
return value != 0.0
|
|
569
|
+
if isinstance(value, bool):
|
|
570
|
+
return value
|
|
571
|
+
return value
|
|
572
|
+
|
|
573
|
+
@classmethod
|
|
574
|
+
def implicit_cast(cls, value: Any, from_type: Any) -> bool:
|
|
575
|
+
if from_type in {Boolean}:
|
|
576
|
+
return value
|
|
577
|
+
|
|
578
|
+
raise RunTimeError(
|
|
579
|
+
"2-1-5-1",
|
|
580
|
+
value=value,
|
|
581
|
+
type_1=SCALAR_TYPES_CLASS_REVERSE[from_type],
|
|
582
|
+
type_2=SCALAR_TYPES_CLASS_REVERSE[cls],
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
@classmethod
|
|
586
|
+
def explicit_cast(cls, value: Any, from_type: Any) -> bool:
|
|
587
|
+
if from_type in {Number, Integer}:
|
|
588
|
+
return value not in {0}
|
|
589
|
+
|
|
590
|
+
raise RunTimeError(
|
|
591
|
+
"2-1-5-1",
|
|
592
|
+
value=value,
|
|
593
|
+
type_1=SCALAR_TYPES_CLASS_REVERSE[from_type],
|
|
594
|
+
type_2=SCALAR_TYPES_CLASS_REVERSE[cls],
|
|
595
|
+
)
|
|
596
|
+
|
|
597
|
+
@classmethod
|
|
598
|
+
def check(cls, value: Any) -> bool:
|
|
599
|
+
if pd.isnull(value):
|
|
600
|
+
return True
|
|
601
|
+
if isinstance(value, str):
|
|
602
|
+
return value.lower() in {"true", "false", "1", "0"}
|
|
603
|
+
return isinstance(value, (int, float, bool))
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
class Null(ScalarType):
|
|
607
|
+
""" """
|
|
608
|
+
|
|
609
|
+
@classmethod
|
|
610
|
+
def is_null_type(cls) -> bool:
|
|
611
|
+
return True
|
|
612
|
+
|
|
613
|
+
@classmethod
|
|
614
|
+
def check_type(cls, value: Any) -> bool:
|
|
615
|
+
return True
|
|
616
|
+
|
|
617
|
+
@classmethod
|
|
618
|
+
def cast(cls, value: Any) -> None:
|
|
619
|
+
return None
|
|
620
|
+
|
|
621
|
+
@classmethod
|
|
622
|
+
def dtype(cls) -> str:
|
|
623
|
+
return "string"
|
|
624
|
+
|
|
625
|
+
@classmethod
|
|
626
|
+
def check(cls, value: Any) -> bool:
|
|
627
|
+
return True
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
SCALAR_TYPES: Dict[str, Type[ScalarType]] = {
|
|
631
|
+
"String": String,
|
|
632
|
+
"Number": Number,
|
|
633
|
+
"Integer": Integer,
|
|
634
|
+
"Time": TimeInterval,
|
|
635
|
+
"Date": Date,
|
|
636
|
+
"Time_Period": TimePeriod,
|
|
637
|
+
"Duration": Duration,
|
|
638
|
+
"Boolean": Boolean,
|
|
639
|
+
"Null": Null,
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
SCALAR_TYPES_CLASS_REVERSE: Dict[Any, str] = {
|
|
643
|
+
String: "String",
|
|
644
|
+
Number: "Number",
|
|
645
|
+
Integer: "Integer",
|
|
646
|
+
TimeInterval: "Time",
|
|
647
|
+
Date: "Date",
|
|
648
|
+
TimePeriod: "Time_Period",
|
|
649
|
+
Duration: "Duration",
|
|
650
|
+
Boolean: "Boolean",
|
|
651
|
+
Null: "Null",
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
BASIC_TYPES: Dict[type, Type[ScalarType]] = {
|
|
655
|
+
str: String,
|
|
656
|
+
int: Integer,
|
|
657
|
+
float: Number,
|
|
658
|
+
bool: Boolean,
|
|
659
|
+
type(None): Null,
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
COMP_NAME_MAPPING: Dict[Type[ScalarType], str] = {
|
|
663
|
+
String: "str_var",
|
|
664
|
+
Number: "num_var",
|
|
665
|
+
Integer: "int_var",
|
|
666
|
+
TimeInterval: "time_var",
|
|
667
|
+
TimePeriod: "time_period_var",
|
|
668
|
+
Date: "date_var",
|
|
669
|
+
Duration: "duration_var",
|
|
670
|
+
Boolean: "bool_var",
|
|
671
|
+
Null: "null_var",
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
IMPLICIT_TYPE_PROMOTION_MAPPING: Dict[Type[ScalarType], Any] = {
|
|
675
|
+
# TODO: Remove Time types, only for compatibility with previous engine
|
|
676
|
+
String: {String, Boolean, TimePeriod},
|
|
677
|
+
Number: {String, Number, Integer},
|
|
678
|
+
Integer: {String, Number, Integer},
|
|
679
|
+
# TODO: Remove String, only for compatibility with previous engine
|
|
680
|
+
TimeInterval: {TimeInterval},
|
|
681
|
+
Date: {TimeInterval, Date},
|
|
682
|
+
TimePeriod: {TimeInterval, TimePeriod},
|
|
683
|
+
Duration: {Duration},
|
|
684
|
+
Boolean: {String, Boolean},
|
|
685
|
+
Null: {
|
|
686
|
+
String,
|
|
687
|
+
Number,
|
|
688
|
+
Integer,
|
|
689
|
+
TimeInterval,
|
|
690
|
+
Date,
|
|
691
|
+
TimePeriod,
|
|
692
|
+
Duration,
|
|
693
|
+
Boolean,
|
|
694
|
+
Null,
|
|
695
|
+
},
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
# TODO: Implicit are valid as cast without mask
|
|
699
|
+
EXPLICIT_WITHOUT_MASK_TYPE_PROMOTION_MAPPING: Dict[Type[ScalarType], Any] = {
|
|
700
|
+
# TODO: Remove time types, only for compatibility with previous engine
|
|
701
|
+
String: {Integer, String, Date, TimePeriod, TimeInterval, Duration, Number},
|
|
702
|
+
Number: {Integer, Boolean, String, Number},
|
|
703
|
+
Integer: {Number, Boolean, String, Integer},
|
|
704
|
+
# TODO: Remove String on time types, only for compatibility with previous engine
|
|
705
|
+
TimeInterval: {TimeInterval, String},
|
|
706
|
+
Date: {TimePeriod, Date, String},
|
|
707
|
+
TimePeriod: {TimePeriod, String},
|
|
708
|
+
Duration: {Duration, String},
|
|
709
|
+
Boolean: {Integer, Number, String, Boolean},
|
|
710
|
+
Null: {
|
|
711
|
+
String,
|
|
712
|
+
Number,
|
|
713
|
+
Integer,
|
|
714
|
+
TimeInterval,
|
|
715
|
+
Date,
|
|
716
|
+
TimePeriod,
|
|
717
|
+
Duration,
|
|
718
|
+
Boolean,
|
|
719
|
+
Null,
|
|
720
|
+
},
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
EXPLICIT_WITH_MASK_TYPE_PROMOTION_MAPPING: Dict[Type[ScalarType], Any] = {
|
|
724
|
+
String: {Number, TimeInterval, Date, TimePeriod, Duration},
|
|
725
|
+
Number: {},
|
|
726
|
+
Integer: {},
|
|
727
|
+
TimeInterval: {String},
|
|
728
|
+
Date: {String},
|
|
729
|
+
TimePeriod: {Date},
|
|
730
|
+
Duration: {String},
|
|
731
|
+
Boolean: {},
|
|
732
|
+
Null: {
|
|
733
|
+
String,
|
|
734
|
+
Number,
|
|
735
|
+
Integer,
|
|
736
|
+
TimeInterval,
|
|
737
|
+
Date,
|
|
738
|
+
TimePeriod,
|
|
739
|
+
Duration,
|
|
740
|
+
Boolean,
|
|
741
|
+
Null,
|
|
742
|
+
},
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
|
|
746
|
+
def binary_implicit_promotion(
|
|
747
|
+
left_type: Type[ScalarType],
|
|
748
|
+
right_type: Type[ScalarType],
|
|
749
|
+
type_to_check: Optional[Type[ScalarType]] = None,
|
|
750
|
+
return_type: Optional[Type[ScalarType]] = None,
|
|
751
|
+
) -> Type[ScalarType]:
|
|
752
|
+
"""
|
|
753
|
+
Validates the compatibility between the types of the operands and the operator
|
|
754
|
+
(implicit type promotion : check_binary_implicit_type_promotion)
|
|
755
|
+
:param left: The left operand
|
|
756
|
+
:param right: The right operand
|
|
757
|
+
:return: The resulting type of the operation, after the implicit type promotion
|
|
758
|
+
"""
|
|
759
|
+
left_implicities = IMPLICIT_TYPE_PROMOTION_MAPPING[left_type]
|
|
760
|
+
right_implicities = IMPLICIT_TYPE_PROMOTION_MAPPING[right_type]
|
|
761
|
+
if type_to_check is not None:
|
|
762
|
+
if type_to_check.is_included(left_implicities.intersection(right_implicities)):
|
|
763
|
+
if return_type is not None:
|
|
764
|
+
return return_type
|
|
765
|
+
if left_type.is_included(right_implicities):
|
|
766
|
+
if left_type.is_subtype(right_type): # For Integer and Number
|
|
767
|
+
return right_type
|
|
768
|
+
elif right_type.is_subtype(left_type):
|
|
769
|
+
return left_type
|
|
770
|
+
return left_type
|
|
771
|
+
if right_type.is_included(left_implicities):
|
|
772
|
+
return right_type
|
|
773
|
+
return type_to_check
|
|
774
|
+
raise SemanticError(
|
|
775
|
+
code="1-1-1-2",
|
|
776
|
+
type_1=SCALAR_TYPES_CLASS_REVERSE[left_type],
|
|
777
|
+
type_2=SCALAR_TYPES_CLASS_REVERSE[right_type],
|
|
778
|
+
type_check=SCALAR_TYPES_CLASS_REVERSE[type_to_check],
|
|
779
|
+
)
|
|
780
|
+
# raise Exception(f"Implicit cast not allowed from
|
|
781
|
+
# {left_type} and {right_type} to {type_to_check}")
|
|
782
|
+
|
|
783
|
+
if return_type and (
|
|
784
|
+
left_type.is_included(right_implicities) or right_type.is_included(left_implicities)
|
|
785
|
+
):
|
|
786
|
+
return return_type
|
|
787
|
+
if left_type.is_included(right_implicities):
|
|
788
|
+
return left_type
|
|
789
|
+
if right_type.is_included(left_implicities):
|
|
790
|
+
return right_type
|
|
791
|
+
|
|
792
|
+
raise SemanticError(
|
|
793
|
+
code="1-1-1-1",
|
|
794
|
+
type_1=SCALAR_TYPES_CLASS_REVERSE[left_type],
|
|
795
|
+
type_2=SCALAR_TYPES_CLASS_REVERSE[right_type],
|
|
796
|
+
)
|
|
797
|
+
|
|
798
|
+
|
|
799
|
+
def check_binary_implicit_promotion(
|
|
800
|
+
left: Type[ScalarType],
|
|
801
|
+
right: Any,
|
|
802
|
+
type_to_check: Any = None,
|
|
803
|
+
return_type: Any = None,
|
|
804
|
+
) -> bool:
|
|
805
|
+
"""
|
|
806
|
+
Validates the compatibility between the types of the operands and the operator
|
|
807
|
+
(implicit type promotion : check_binary_implicit_type_promotion)
|
|
808
|
+
:param left: The left operand
|
|
809
|
+
:param right: The right operand
|
|
810
|
+
:param type_to_check: The type of the operator (from the operator if any)
|
|
811
|
+
:param return_type: The type of the result (from the operator if any)
|
|
812
|
+
:return: True if the types are compatible, False otherwise
|
|
813
|
+
"""
|
|
814
|
+
left_implicities = IMPLICIT_TYPE_PROMOTION_MAPPING[left]
|
|
815
|
+
right_implicities = IMPLICIT_TYPE_PROMOTION_MAPPING[right]
|
|
816
|
+
if type_to_check:
|
|
817
|
+
return type_to_check.is_included(set_=left_implicities.intersection(right_implicities))
|
|
818
|
+
|
|
819
|
+
return left.is_included(right_implicities) or right.is_included(left_implicities)
|
|
820
|
+
|
|
821
|
+
|
|
822
|
+
def unary_implicit_promotion(
|
|
823
|
+
operand_type: Type[ScalarType],
|
|
824
|
+
type_to_check: Optional[Type[ScalarType]] = None,
|
|
825
|
+
return_type: Optional[Type[ScalarType]] = None,
|
|
826
|
+
) -> Type[ScalarType]:
|
|
827
|
+
"""
|
|
828
|
+
Validates the compatibility between the type of the operand and the operator
|
|
829
|
+
param operand: The operand
|
|
830
|
+
param type_to_check: The type of the operator (from the operator if any)
|
|
831
|
+
param return_type: The type of the result (from the operator if any)
|
|
832
|
+
return: The resulting type of the operation, after the implicit type promotion
|
|
833
|
+
"""
|
|
834
|
+
operand_implicities = IMPLICIT_TYPE_PROMOTION_MAPPING[operand_type]
|
|
835
|
+
if type_to_check and not type_to_check.is_included(operand_implicities):
|
|
836
|
+
raise SemanticError(
|
|
837
|
+
code="1-1-1-1",
|
|
838
|
+
type_1=SCALAR_TYPES_CLASS_REVERSE[operand_type],
|
|
839
|
+
type_2=SCALAR_TYPES_CLASS_REVERSE[type_to_check],
|
|
840
|
+
)
|
|
841
|
+
if return_type:
|
|
842
|
+
return return_type
|
|
843
|
+
if (
|
|
844
|
+
type_to_check
|
|
845
|
+
and not issubclass(operand_type, type_to_check)
|
|
846
|
+
and not issubclass(type_to_check, operand_type)
|
|
847
|
+
):
|
|
848
|
+
return type_to_check
|
|
849
|
+
return operand_type
|
|
850
|
+
|
|
851
|
+
|
|
852
|
+
def check_unary_implicit_promotion(
|
|
853
|
+
operand_type: Type[ScalarType], type_to_check: Any = None, return_type: Any = None
|
|
854
|
+
) -> bool:
|
|
855
|
+
"""
|
|
856
|
+
Validates the compatibility between the type of the operand and the operator
|
|
857
|
+
:param operand: The operand
|
|
858
|
+
:param type_to_check: The type of the operator (from the operator if any)
|
|
859
|
+
:param return_type: The type of the result (from the operator if any)
|
|
860
|
+
:return: True if the types are compatible, False otherwise
|
|
861
|
+
"""
|
|
862
|
+
operand_implicities = IMPLICIT_TYPE_PROMOTION_MAPPING[operand_type]
|
|
863
|
+
return not (type_to_check and not type_to_check.is_included(operand_implicities))
|