vtlengine 1.0.3rc2__py3-none-any.whl → 1.0.4__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.

Files changed (47) hide show
  1. vtlengine/API/_InternalApi.py +55 -20
  2. vtlengine/API/__init__.py +11 -2
  3. vtlengine/API/data/schema/json_schema_2.1.json +116 -0
  4. vtlengine/AST/ASTConstructor.py +5 -4
  5. vtlengine/AST/ASTConstructorModules/Expr.py +47 -48
  6. vtlengine/AST/ASTConstructorModules/ExprComponents.py +45 -23
  7. vtlengine/AST/ASTConstructorModules/Terminals.py +21 -11
  8. vtlengine/AST/ASTEncoders.py +1 -1
  9. vtlengine/AST/DAG/__init__.py +0 -3
  10. vtlengine/AST/Grammar/lexer.py +0 -1
  11. vtlengine/AST/Grammar/parser.py +185 -440
  12. vtlengine/AST/VtlVisitor.py +0 -1
  13. vtlengine/DataTypes/TimeHandling.py +50 -15
  14. vtlengine/DataTypes/__init__.py +79 -7
  15. vtlengine/Exceptions/__init__.py +3 -5
  16. vtlengine/Exceptions/messages.py +65 -105
  17. vtlengine/Interpreter/__init__.py +83 -38
  18. vtlengine/Model/__init__.py +7 -9
  19. vtlengine/Operators/Aggregation.py +13 -7
  20. vtlengine/Operators/Analytic.py +48 -9
  21. vtlengine/Operators/Assignment.py +0 -1
  22. vtlengine/Operators/CastOperator.py +44 -44
  23. vtlengine/Operators/Clause.py +16 -10
  24. vtlengine/Operators/Comparison.py +20 -12
  25. vtlengine/Operators/Conditional.py +30 -13
  26. vtlengine/Operators/General.py +9 -4
  27. vtlengine/Operators/HROperators.py +4 -14
  28. vtlengine/Operators/Join.py +15 -14
  29. vtlengine/Operators/Numeric.py +32 -26
  30. vtlengine/Operators/RoleSetter.py +6 -2
  31. vtlengine/Operators/Set.py +12 -8
  32. vtlengine/Operators/String.py +9 -9
  33. vtlengine/Operators/Time.py +136 -116
  34. vtlengine/Operators/Validation.py +10 -4
  35. vtlengine/Operators/__init__.py +56 -69
  36. vtlengine/Utils/__init__.py +6 -1
  37. vtlengine/files/output/__init__.py +0 -1
  38. vtlengine/files/output/_time_period_representation.py +2 -1
  39. vtlengine/files/parser/__init__.py +44 -10
  40. vtlengine/files/parser/_rfc_dialect.py +1 -1
  41. vtlengine/files/parser/_time_checking.py +4 -4
  42. {vtlengine-1.0.3rc2.dist-info → vtlengine-1.0.4.dist-info}/METADATA +9 -7
  43. vtlengine-1.0.4.dist-info/RECORD +58 -0
  44. {vtlengine-1.0.3rc2.dist-info → vtlengine-1.0.4.dist-info}/WHEEL +1 -1
  45. vtlengine/DataTypes/NumericTypesHandling.py +0 -38
  46. vtlengine-1.0.3rc2.dist-info/RECORD +0 -58
  47. {vtlengine-1.0.3rc2.dist-info → vtlengine-1.0.4.dist-info}/LICENSE.md +0 -0
@@ -6,7 +6,6 @@ from vtlengine.AST.Grammar.parser import Parser
6
6
 
7
7
 
8
8
  class VtlVisitor(ParseTreeVisitor):
9
-
10
9
  # Visit a parse tree produced by Parser#start.
11
10
  def visitStart(self, ctx: Parser.StartContext):
12
11
  return self.visitChildren(ctx)
@@ -9,9 +9,9 @@ import pandas as pd
9
9
 
10
10
  from vtlengine.Exceptions import SemanticError
11
11
 
12
- DURATION_MAPPING = {"A": 6, "S": 5, "Q": 4, "M": 3, "W": 2, "D": 1}
12
+ PERIOD_IND_MAPPING = {"A": 6, "S": 5, "Q": 4, "M": 3, "W": 2, "D": 1}
13
13
 
14
- DURATION_MAPPING_REVERSED = {6: "A", 5: "S", 4: "Q", 3: "M", 2: "W", 1: "D"}
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(year: int,
36
- period_indicator: str,
37
- period_number: int,
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": [(date(year, 1, 1), date(year, 6, 30)), (date(year, 7, 1), date(year, 12, 31))],
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
- return py_op(
240
- DURATION_MAPPING[self.period_indicator], DURATION_MAPPING[other.period_indicator]
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, DURATION_MAPPING[s.period_indicator], s.period_number),
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] == "-":
@@ -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 check_max_date, date_to_period_str, str_period_to_date
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 in {Duration, String}:
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: {String, Number, Integer, TimeInterval, Date, TimePeriod, Duration, Boolean, 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: {String, Number, Integer, TimeInterval, Date, TimePeriod, Duration, Boolean, 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: {String, Number, Integer, TimeInterval, Date, TimePeriod, Duration, Boolean, 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], right: Any, type_to_check: Any = None, return_type: Any = None
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
@@ -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('inf')
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]