vtlengine 1.0.3rc3__py3-none-any.whl → 1.1rc1__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.
Potentially problematic release.
This version of vtlengine might be problematic. Click here for more details.
- vtlengine/API/_InternalApi.py +64 -58
- vtlengine/API/__init__.py +11 -2
- vtlengine/API/data/schema/json_schema_2.1.json +116 -0
- vtlengine/AST/ASTConstructor.py +5 -4
- vtlengine/AST/ASTConstructorModules/Expr.py +47 -48
- vtlengine/AST/ASTConstructorModules/ExprComponents.py +45 -23
- vtlengine/AST/ASTConstructorModules/Terminals.py +21 -11
- vtlengine/AST/ASTEncoders.py +1 -1
- vtlengine/AST/DAG/__init__.py +0 -3
- vtlengine/AST/Grammar/lexer.py +0 -1
- vtlengine/AST/Grammar/parser.py +185 -440
- vtlengine/AST/VtlVisitor.py +0 -1
- vtlengine/DataTypes/TimeHandling.py +50 -15
- vtlengine/DataTypes/__init__.py +79 -7
- vtlengine/Exceptions/__init__.py +3 -5
- vtlengine/Exceptions/messages.py +65 -105
- vtlengine/Interpreter/__init__.py +83 -38
- vtlengine/Model/__init__.py +7 -9
- vtlengine/Operators/Aggregation.py +13 -7
- vtlengine/Operators/Analytic.py +48 -9
- vtlengine/Operators/Assignment.py +0 -1
- vtlengine/Operators/CastOperator.py +44 -44
- vtlengine/Operators/Clause.py +16 -10
- vtlengine/Operators/Comparison.py +20 -12
- vtlengine/Operators/Conditional.py +30 -13
- vtlengine/Operators/General.py +9 -4
- vtlengine/Operators/HROperators.py +4 -14
- vtlengine/Operators/Join.py +15 -14
- vtlengine/Operators/Numeric.py +32 -26
- vtlengine/Operators/RoleSetter.py +6 -2
- vtlengine/Operators/Set.py +12 -8
- vtlengine/Operators/String.py +9 -9
- vtlengine/Operators/Time.py +136 -116
- vtlengine/Operators/Validation.py +10 -4
- vtlengine/Operators/__init__.py +56 -69
- vtlengine/Utils/__init__.py +6 -1
- vtlengine/__extras_check.py +17 -0
- vtlengine/files/output/__init__.py +2 -1
- vtlengine/files/output/_time_period_representation.py +2 -1
- vtlengine/files/parser/__init__.py +47 -31
- vtlengine/files/parser/_rfc_dialect.py +1 -1
- vtlengine/files/parser/_time_checking.py +4 -4
- {vtlengine-1.0.3rc3.dist-info → vtlengine-1.1rc1.dist-info}/METADATA +17 -17
- vtlengine-1.1rc1.dist-info/RECORD +59 -0
- {vtlengine-1.0.3rc3.dist-info → vtlengine-1.1rc1.dist-info}/WHEEL +1 -1
- vtlengine/DataTypes/NumericTypesHandling.py +0 -38
- vtlengine-1.0.3rc3.dist-info/RECORD +0 -58
- {vtlengine-1.0.3rc3.dist-info → vtlengine-1.1rc1.dist-info}/LICENSE.md +0 -0
vtlengine/AST/VtlVisitor.py
CHANGED
|
@@ -9,9 +9,9 @@ import pandas as pd
|
|
|
9
9
|
|
|
10
10
|
from vtlengine.Exceptions import SemanticError
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
PERIOD_IND_MAPPING = {"A": 6, "S": 5, "Q": 4, "M": 3, "W": 2, "D": 1}
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
PERIOD_IND_MAPPING_REVERSE = {6: "A", 5: "S", 4: "Q", 3: "M", 2: "W", 1: "D"}
|
|
15
15
|
|
|
16
16
|
PERIOD_INDICATORS = ["A", "S", "Q", "M", "W", "D"]
|
|
17
17
|
|
|
@@ -32,15 +32,16 @@ def date_to_period(date_value: date, period_indicator: str) -> Any:
|
|
|
32
32
|
return TimePeriodHandler(f"{date_value.year}D{date_value.timetuple().tm_yday}")
|
|
33
33
|
|
|
34
34
|
|
|
35
|
-
def period_to_date(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
start: bool = False
|
|
39
|
-
) -> date:
|
|
35
|
+
def period_to_date(
|
|
36
|
+
year: int, period_indicator: str, period_number: int, start: bool = False
|
|
37
|
+
) -> date:
|
|
40
38
|
if period_indicator == "A":
|
|
41
39
|
return date(year, 1, 1) if start else date(year, 12, 31)
|
|
42
40
|
periods = {
|
|
43
|
-
"S": [
|
|
41
|
+
"S": [
|
|
42
|
+
(date(year, 1, 1), date(year, 6, 30)),
|
|
43
|
+
(date(year, 7, 1), date(year, 12, 31)),
|
|
44
|
+
],
|
|
44
45
|
"Q": [
|
|
45
46
|
(date(year, 1, 1), date(year, 3, 31)),
|
|
46
47
|
(date(year, 4, 1), date(year, 6, 30)),
|
|
@@ -202,6 +203,10 @@ class TimePeriodHandler:
|
|
|
202
203
|
raise SemanticError("2-1-19-2", period=value)
|
|
203
204
|
self._period_indicator = value
|
|
204
205
|
|
|
206
|
+
@property
|
|
207
|
+
def period_magnitude(self) -> int:
|
|
208
|
+
return PERIOD_IND_MAPPING[self.period_indicator]
|
|
209
|
+
|
|
205
210
|
@property
|
|
206
211
|
def period_number(self) -> int:
|
|
207
212
|
return self._period_number
|
|
@@ -229,16 +234,46 @@ class TimePeriodHandler:
|
|
|
229
234
|
# raise ValueError(f'Invalid day {value} for year {self.year}.')
|
|
230
235
|
self._period_number = value
|
|
231
236
|
|
|
237
|
+
@property
|
|
238
|
+
def period_dates(self) -> tuple[date, date]:
|
|
239
|
+
return (
|
|
240
|
+
period_to_date(self.year, self.period_indicator, self.period_number, start=True),
|
|
241
|
+
period_to_date(self.year, self.period_indicator, self.period_number, start=False),
|
|
242
|
+
)
|
|
243
|
+
|
|
232
244
|
def _meta_comparison(self, other: Any, py_op: Any) -> Optional[bool]:
|
|
233
245
|
if pd.isnull(other):
|
|
234
246
|
return None
|
|
247
|
+
|
|
248
|
+
if py_op in (operator.eq, operator.ne):
|
|
249
|
+
return py_op(str(self), str(other))
|
|
250
|
+
|
|
251
|
+
if py_op in (operator.ge, operator.le) and str(self) == str(other):
|
|
252
|
+
return True
|
|
253
|
+
|
|
235
254
|
if isinstance(other, str):
|
|
236
|
-
if len(other) == 0:
|
|
237
|
-
return False
|
|
238
255
|
other = TimePeriodHandler(other)
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
256
|
+
|
|
257
|
+
self_lapse, other_lapse = self.period_dates, other.period_dates
|
|
258
|
+
is_lt_or_le = py_op in [operator.lt, operator.le]
|
|
259
|
+
is_gt_or_ge = py_op in [operator.gt, operator.ge]
|
|
260
|
+
|
|
261
|
+
if is_lt_or_le or is_gt_or_ge:
|
|
262
|
+
idx = 0 if is_lt_or_le else 1
|
|
263
|
+
if self_lapse[idx] != other_lapse[idx]:
|
|
264
|
+
return (
|
|
265
|
+
self_lapse[idx] < other_lapse[idx]
|
|
266
|
+
if is_lt_or_le
|
|
267
|
+
else self_lapse[idx] > other_lapse[idx]
|
|
268
|
+
)
|
|
269
|
+
if self.period_magnitude != other.period_magnitude:
|
|
270
|
+
return (
|
|
271
|
+
self.period_magnitude < other.period_magnitude
|
|
272
|
+
if is_lt_or_le
|
|
273
|
+
else self.period_magnitude > other.period_magnitude
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
return False
|
|
242
277
|
|
|
243
278
|
def start_date(self, as_date: bool = False) -> Union[date, str]:
|
|
244
279
|
"""
|
|
@@ -453,7 +488,7 @@ def shift_period(x: TimePeriodHandler, shift_param: int) -> TimePeriodHandler:
|
|
|
453
488
|
def sort_time_period(series: Any) -> Any:
|
|
454
489
|
values_sorted = sorted(
|
|
455
490
|
series.to_list(),
|
|
456
|
-
key=lambda s: (s.year,
|
|
491
|
+
key=lambda s: (s.year, PERIOD_IND_MAPPING[s.period_indicator], s.period_number),
|
|
457
492
|
)
|
|
458
493
|
return pd.Series(values_sorted, name=series.name)
|
|
459
494
|
|
|
@@ -478,7 +513,7 @@ def generate_period_range(
|
|
|
478
513
|
|
|
479
514
|
|
|
480
515
|
def check_max_date(str_: Optional[str]) -> Optional[str]:
|
|
481
|
-
if pd.isnull(str_) or str_ == "nan" or str_ == "NaT":
|
|
516
|
+
if pd.isnull(str_) or str_ == "nan" or str_ == "NaT" or str_ is None:
|
|
482
517
|
return None
|
|
483
518
|
|
|
484
519
|
if len(str_) == 9 and str_[7] == "-":
|
vtlengine/DataTypes/__init__.py
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
import re
|
|
1
2
|
from typing import Any, Dict, Optional, Set, Type, Union
|
|
2
3
|
|
|
3
4
|
import pandas as pd
|
|
4
5
|
|
|
5
|
-
from vtlengine.DataTypes.TimeHandling import
|
|
6
|
+
from vtlengine.DataTypes.TimeHandling import (
|
|
7
|
+
check_max_date,
|
|
8
|
+
date_to_period_str,
|
|
9
|
+
str_period_to_date,
|
|
10
|
+
)
|
|
6
11
|
from vtlengine.Exceptions import SemanticError
|
|
7
12
|
|
|
8
13
|
DTYPE_MAPPING: Dict[str, str] = {
|
|
@@ -397,10 +402,19 @@ class TimePeriod(TimeInterval):
|
|
|
397
402
|
|
|
398
403
|
|
|
399
404
|
class Duration(ScalarType):
|
|
405
|
+
iso8601_duration_pattern = r"^P((\d+Y)?(\d+M)?(\d+D)?)$"
|
|
406
|
+
|
|
407
|
+
@classmethod
|
|
408
|
+
def validate_duration(cls, value: Any) -> bool:
|
|
409
|
+
try:
|
|
410
|
+
match = re.match(cls.iso8601_duration_pattern, value)
|
|
411
|
+
return bool(match)
|
|
412
|
+
except Exception:
|
|
413
|
+
raise Exception("Must be valid")
|
|
400
414
|
|
|
401
415
|
@classmethod
|
|
402
416
|
def implicit_cast(cls, value: Any, from_type: Any) -> str:
|
|
403
|
-
if from_type
|
|
417
|
+
if from_type == String and cls.validate_duration(value):
|
|
404
418
|
return value
|
|
405
419
|
|
|
406
420
|
raise SemanticError(
|
|
@@ -412,7 +426,7 @@ class Duration(ScalarType):
|
|
|
412
426
|
|
|
413
427
|
@classmethod
|
|
414
428
|
def explicit_cast(cls, value: Any, from_type: Any) -> Any:
|
|
415
|
-
if from_type == String:
|
|
429
|
+
if from_type == String and cls.validate_duration(value):
|
|
416
430
|
return value
|
|
417
431
|
|
|
418
432
|
raise SemanticError(
|
|
@@ -422,6 +436,31 @@ class Duration(ScalarType):
|
|
|
422
436
|
type_2=SCALAR_TYPES_CLASS_REVERSE[cls],
|
|
423
437
|
)
|
|
424
438
|
|
|
439
|
+
@classmethod
|
|
440
|
+
def to_days(cls, value: Any) -> int:
|
|
441
|
+
if not cls.validate_duration(value):
|
|
442
|
+
raise SemanticError(
|
|
443
|
+
"2-1-19-15", "{op} can only be applied according to the iso 8601 format mask"
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
match = re.match(cls.iso8601_duration_pattern, value)
|
|
447
|
+
|
|
448
|
+
years = 0
|
|
449
|
+
months = 0
|
|
450
|
+
days = 0
|
|
451
|
+
|
|
452
|
+
years_str = match.group(2) # type: ignore[union-attr]
|
|
453
|
+
months_str = match.group(3) # type: ignore[union-attr]
|
|
454
|
+
days_str = match.group(4) # type: ignore[union-attr]
|
|
455
|
+
if years_str:
|
|
456
|
+
years = int(years_str[:-1])
|
|
457
|
+
if months_str:
|
|
458
|
+
months = int(months_str[:-1])
|
|
459
|
+
if days_str:
|
|
460
|
+
days = int(days_str[:-1])
|
|
461
|
+
total_days = years * 365 + months * 30 + days
|
|
462
|
+
return int(total_days)
|
|
463
|
+
|
|
425
464
|
|
|
426
465
|
class Boolean(ScalarType):
|
|
427
466
|
""" """
|
|
@@ -549,7 +588,17 @@ IMPLICIT_TYPE_PROMOTION_MAPPING: Dict[Type[ScalarType], Any] = {
|
|
|
549
588
|
TimePeriod: {TimeInterval, TimePeriod},
|
|
550
589
|
Duration: {Duration},
|
|
551
590
|
Boolean: {String, Boolean},
|
|
552
|
-
Null: {
|
|
591
|
+
Null: {
|
|
592
|
+
String,
|
|
593
|
+
Number,
|
|
594
|
+
Integer,
|
|
595
|
+
TimeInterval,
|
|
596
|
+
Date,
|
|
597
|
+
TimePeriod,
|
|
598
|
+
Duration,
|
|
599
|
+
Boolean,
|
|
600
|
+
Null,
|
|
601
|
+
},
|
|
553
602
|
}
|
|
554
603
|
|
|
555
604
|
# TODO: Implicit are valid as cast without mask
|
|
@@ -564,7 +613,17 @@ EXPLICIT_WITHOUT_MASK_TYPE_PROMOTION_MAPPING: Dict[Type[ScalarType], Any] = {
|
|
|
564
613
|
TimePeriod: {TimePeriod, String},
|
|
565
614
|
Duration: {Duration, String},
|
|
566
615
|
Boolean: {Integer, Number, String, Boolean},
|
|
567
|
-
Null: {
|
|
616
|
+
Null: {
|
|
617
|
+
String,
|
|
618
|
+
Number,
|
|
619
|
+
Integer,
|
|
620
|
+
TimeInterval,
|
|
621
|
+
Date,
|
|
622
|
+
TimePeriod,
|
|
623
|
+
Duration,
|
|
624
|
+
Boolean,
|
|
625
|
+
Null,
|
|
626
|
+
},
|
|
568
627
|
}
|
|
569
628
|
|
|
570
629
|
EXPLICIT_WITH_MASK_TYPE_PROMOTION_MAPPING: Dict[Type[ScalarType], Any] = {
|
|
@@ -576,7 +635,17 @@ EXPLICIT_WITH_MASK_TYPE_PROMOTION_MAPPING: Dict[Type[ScalarType], Any] = {
|
|
|
576
635
|
TimePeriod: {Date},
|
|
577
636
|
Duration: {String},
|
|
578
637
|
Boolean: {},
|
|
579
|
-
Null: {
|
|
638
|
+
Null: {
|
|
639
|
+
String,
|
|
640
|
+
Number,
|
|
641
|
+
Integer,
|
|
642
|
+
TimeInterval,
|
|
643
|
+
Date,
|
|
644
|
+
TimePeriod,
|
|
645
|
+
Duration,
|
|
646
|
+
Boolean,
|
|
647
|
+
Null,
|
|
648
|
+
},
|
|
580
649
|
}
|
|
581
650
|
|
|
582
651
|
|
|
@@ -634,7 +703,10 @@ def binary_implicit_promotion(
|
|
|
634
703
|
|
|
635
704
|
|
|
636
705
|
def check_binary_implicit_promotion(
|
|
637
|
-
left: Type[ScalarType],
|
|
706
|
+
left: Type[ScalarType],
|
|
707
|
+
right: Any,
|
|
708
|
+
type_to_check: Any = None,
|
|
709
|
+
return_type: Any = None,
|
|
638
710
|
) -> bool:
|
|
639
711
|
"""
|
|
640
712
|
Validates the compatibility between the types of the operands and the operator
|
vtlengine/Exceptions/__init__.py
CHANGED
|
@@ -124,7 +124,7 @@ class InputValidationException(VTLEngineException):
|
|
|
124
124
|
lino: Optional[str] = None,
|
|
125
125
|
colno: Optional[str] = None,
|
|
126
126
|
code: Optional[str] = None,
|
|
127
|
-
**kwargs: Any
|
|
127
|
+
**kwargs: Any,
|
|
128
128
|
) -> None:
|
|
129
129
|
if code is not None:
|
|
130
130
|
message = centralised_messages[code].format(**kwargs)
|
|
@@ -143,7 +143,7 @@ def check_key(field: str, dict_keys: Any, key: str) -> None:
|
|
|
143
143
|
def find_closest_key(dict_keys: Any, key: str) -> Optional[str]:
|
|
144
144
|
closest_key = None
|
|
145
145
|
max_distance = 3
|
|
146
|
-
min_distance = float(
|
|
146
|
+
min_distance = float("inf")
|
|
147
147
|
|
|
148
148
|
for dict_key in dict_keys:
|
|
149
149
|
distance = key_distance(key, dict_key)
|
|
@@ -167,8 +167,6 @@ def key_distance(key: str, objetive: str) -> int:
|
|
|
167
167
|
for i in range(1, len(key) + 1):
|
|
168
168
|
for j in range(1, len(objetive) + 1):
|
|
169
169
|
cost = 0 if key[i - 1] == objetive[j - 1] else 1
|
|
170
|
-
dp[i][j] = min(dp[i - 1][j] + 1,
|
|
171
|
-
dp[i][j - 1] + 1,
|
|
172
|
-
dp[i - 1][j - 1] + cost)
|
|
170
|
+
dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost)
|
|
173
171
|
|
|
174
172
|
return dp[-1][-1]
|