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.
Files changed (66) hide show
  1. vtlengine/API/_InternalApi.py +791 -0
  2. vtlengine/API/__init__.py +612 -0
  3. vtlengine/API/data/schema/external_routines_schema.json +34 -0
  4. vtlengine/API/data/schema/json_schema_2.1.json +116 -0
  5. vtlengine/API/data/schema/value_domain_schema.json +97 -0
  6. vtlengine/AST/ASTComment.py +57 -0
  7. vtlengine/AST/ASTConstructor.py +598 -0
  8. vtlengine/AST/ASTConstructorModules/Expr.py +1928 -0
  9. vtlengine/AST/ASTConstructorModules/ExprComponents.py +995 -0
  10. vtlengine/AST/ASTConstructorModules/Terminals.py +790 -0
  11. vtlengine/AST/ASTConstructorModules/__init__.py +50 -0
  12. vtlengine/AST/ASTDataExchange.py +10 -0
  13. vtlengine/AST/ASTEncoders.py +32 -0
  14. vtlengine/AST/ASTString.py +675 -0
  15. vtlengine/AST/ASTTemplate.py +558 -0
  16. vtlengine/AST/ASTVisitor.py +25 -0
  17. vtlengine/AST/DAG/__init__.py +479 -0
  18. vtlengine/AST/DAG/_words.py +10 -0
  19. vtlengine/AST/Grammar/Vtl.g4 +705 -0
  20. vtlengine/AST/Grammar/VtlTokens.g4 +409 -0
  21. vtlengine/AST/Grammar/__init__.py +0 -0
  22. vtlengine/AST/Grammar/lexer.py +2139 -0
  23. vtlengine/AST/Grammar/parser.py +16597 -0
  24. vtlengine/AST/Grammar/tokens.py +169 -0
  25. vtlengine/AST/VtlVisitor.py +824 -0
  26. vtlengine/AST/__init__.py +674 -0
  27. vtlengine/DataTypes/TimeHandling.py +562 -0
  28. vtlengine/DataTypes/__init__.py +863 -0
  29. vtlengine/DataTypes/_time_checking.py +135 -0
  30. vtlengine/Exceptions/__exception_file_generator.py +96 -0
  31. vtlengine/Exceptions/__init__.py +159 -0
  32. vtlengine/Exceptions/messages.py +1004 -0
  33. vtlengine/Interpreter/__init__.py +2048 -0
  34. vtlengine/Model/__init__.py +501 -0
  35. vtlengine/Operators/Aggregation.py +357 -0
  36. vtlengine/Operators/Analytic.py +455 -0
  37. vtlengine/Operators/Assignment.py +23 -0
  38. vtlengine/Operators/Boolean.py +106 -0
  39. vtlengine/Operators/CastOperator.py +451 -0
  40. vtlengine/Operators/Clause.py +366 -0
  41. vtlengine/Operators/Comparison.py +488 -0
  42. vtlengine/Operators/Conditional.py +495 -0
  43. vtlengine/Operators/General.py +191 -0
  44. vtlengine/Operators/HROperators.py +254 -0
  45. vtlengine/Operators/Join.py +447 -0
  46. vtlengine/Operators/Numeric.py +422 -0
  47. vtlengine/Operators/RoleSetter.py +77 -0
  48. vtlengine/Operators/Set.py +176 -0
  49. vtlengine/Operators/String.py +578 -0
  50. vtlengine/Operators/Time.py +1144 -0
  51. vtlengine/Operators/Validation.py +275 -0
  52. vtlengine/Operators/__init__.py +900 -0
  53. vtlengine/Utils/__Virtual_Assets.py +34 -0
  54. vtlengine/Utils/__init__.py +479 -0
  55. vtlengine/__extras_check.py +17 -0
  56. vtlengine/__init__.py +27 -0
  57. vtlengine/files/__init__.py +0 -0
  58. vtlengine/files/output/__init__.py +35 -0
  59. vtlengine/files/output/_time_period_representation.py +55 -0
  60. vtlengine/files/parser/__init__.py +240 -0
  61. vtlengine/files/parser/_rfc_dialect.py +22 -0
  62. vtlengine/py.typed +0 -0
  63. vtlengine-1.4.0rc2.dist-info/METADATA +89 -0
  64. vtlengine-1.4.0rc2.dist-info/RECORD +66 -0
  65. vtlengine-1.4.0rc2.dist-info/WHEEL +4 -0
  66. vtlengine-1.4.0rc2.dist-info/licenses/LICENSE.md +661 -0
@@ -0,0 +1,135 @@
1
+ import calendar
2
+ import re
3
+ from datetime import date, datetime
4
+
5
+ from vtlengine.DataTypes.TimeHandling import TimePeriodHandler
6
+ from vtlengine.Exceptions import InputValidationException
7
+
8
+
9
+ def check_date(value: str) -> str:
10
+ """
11
+ Check if the date is in the correct format.
12
+ """
13
+ # Remove all whitespaces
14
+ value = value.strip()
15
+ try:
16
+ if len(value) == 9 and value[7] == "-":
17
+ value = value[:-1] + "0" + value[-1]
18
+ date_value = date.fromisoformat(value)
19
+ except ValueError as e:
20
+ if "is out of range" in str(e):
21
+ raise InputValidationException(f"Date {value} is out of range for the month.")
22
+ if "month must be in 1..12" in str(e):
23
+ raise InputValidationException(
24
+ f"Date {value} is invalid. Month must be between 1 and 12."
25
+ )
26
+ raise InputValidationException(
27
+ f"Date {value} is not in the correct format. Use YYYY-MM-DD."
28
+ )
29
+
30
+ # Check date is between 1900 and 9999
31
+ if not 1800 <= date_value.year <= 9999:
32
+ raise InputValidationException(
33
+ f"Date {value} is invalid. Year must be between 1900 and 9999."
34
+ )
35
+
36
+ return date_value.isoformat()
37
+
38
+
39
+ def dates_to_string(date1: date, date2: date) -> str:
40
+ date1_str = date1.strftime("%Y-%m-%d")
41
+ date2_str = date2.strftime("%Y-%m-%d")
42
+ return f"{date1_str}/{date2_str}"
43
+
44
+
45
+ date_pattern = r"\d{4}[-][0-1]?\d[-][0-3]?\d"
46
+ year_pattern = r"\d{4}"
47
+ month_pattern = r"\d{4}[-][0-1]?\d"
48
+ time_pattern = r"^" + date_pattern + r"/" + date_pattern + r"$"
49
+
50
+
51
+ def check_time(value: str) -> str:
52
+ value = value.strip()
53
+ year_result = re.fullmatch(year_pattern, value)
54
+ if year_result is not None:
55
+ date1_time = datetime.strptime(value, "%Y")
56
+ date2_time = date1_time.replace(day=31, month=12)
57
+ return dates_to_string(date1_time, date2_time)
58
+ month_result = re.fullmatch(month_pattern, value)
59
+ if month_result is not None:
60
+ date1_time = datetime.strptime(value, "%Y-%m")
61
+ last_month_day = calendar.monthrange(date1_time.year, date1_time.month)[1]
62
+ date2_time = date1_time.replace(day=last_month_day)
63
+ return dates_to_string(date1_time, date2_time)
64
+ time_result = re.fullmatch(time_pattern, value)
65
+ if time_result is not None:
66
+ time_list = value.split("/")
67
+ if time_list[0] > time_list[1]:
68
+ raise ValueError("Start date is greater than end date.")
69
+ return value
70
+ raise ValueError(
71
+ "Time is not in the correct format. Use YYYY-MM-DD/YYYY-MM-DD or YYYY or YYYY-MM."
72
+ )
73
+
74
+
75
+ day_period_pattern = r"^\d{4}[-][0-1]?\d[-][0-3]?\d$"
76
+ month_period_pattern = r"^\d{4}[-][0-1]?\d$"
77
+ year_period_pattern = r"^\d{4}$"
78
+ period_pattern = (
79
+ r"^\d{4}[A]$|^\d{4}[S][1-2]$|^\d{4}[Q][1-4]$|^\d{4}[M]"
80
+ r"[0-1]?\d$|^\d{4}[W][0-5]?\d$|^\d{4}[D][0-3]?[0-9]?\d$"
81
+ )
82
+
83
+ # Related with gitlab issue #440, we can say that period pattern
84
+ # matches with our internal representation (or vtl user manual)
85
+ # and further_options_period_pattern matches
86
+ # with other kinds of inputs that we have to accept for the period.
87
+ further_options_period_pattern = (
88
+ r"\d{4}-\d{2}-\d{2}|^\d{4}-D[0-3]\d\d$|^\d{4}-W([0-4]"
89
+ r"\d|5[0-3])|^\d{4}-(0[1-9]|1[0-2]|M(0[1-9]|1[0-2]|[1-9]))$|^"
90
+ r"\d{4}-Q[1-4]$|^\d{4}-S[1-2]$|^\d{4}-A1$"
91
+ )
92
+
93
+
94
+ def check_time_period(value: str) -> str:
95
+ if isinstance(value, int):
96
+ value = str(value)
97
+ value = value.strip()
98
+
99
+ match = re.fullmatch(r"^(\d{4})-(\d{2})$", value)
100
+ if match:
101
+ value = f"{match.group(1)}-M{match.group(2)}"
102
+
103
+ period_result = re.fullmatch(period_pattern, value)
104
+ if period_result is not None:
105
+ result = TimePeriodHandler(value)
106
+ return str(result)
107
+
108
+ # We allow the user to input the time period in different formats.
109
+ # See gl-440 or documentation in time period tests.
110
+ further_options_period_result = re.fullmatch(further_options_period_pattern, value)
111
+ if further_options_period_result is not None:
112
+ result = TimePeriodHandler(value)
113
+ return str(result)
114
+
115
+ year_result = re.fullmatch(year_period_pattern, value)
116
+ if year_result is not None:
117
+ year = datetime.strptime(value, "%Y")
118
+ year_period_wo_A = str(year.year)
119
+ return year_period_wo_A
120
+ # return year_period
121
+
122
+ month_result = re.fullmatch(month_period_pattern, value)
123
+ if month_result is not None:
124
+ month = datetime.strptime(value, "%Y-%m")
125
+ month_period = month.strftime("%YM%m")
126
+ result = TimePeriodHandler(month_period)
127
+ return str(result)
128
+
129
+ # TODO: Do we use this?
130
+ day_result = re.fullmatch(day_period_pattern, value)
131
+ if day_result is not None:
132
+ day = datetime.strptime(value, "%Y-%m-%d")
133
+ day_period = day.strftime("%YD%-j")
134
+ return day_period
135
+ raise ValueError
@@ -0,0 +1,96 @@
1
+ from pathlib import Path
2
+ from typing import Any, Dict
3
+
4
+ output_filepath = Path(__file__).parent.parent.parent.parent / "Docs" / "error_messages.rst"
5
+
6
+
7
+ def generate_errors_rst(file_path: Path, messages: Dict[str, Any]) -> None:
8
+ """
9
+ Generates an RST file with a grid of error codes, messages, and descriptions.
10
+ """
11
+
12
+ def sort_key(code: str) -> Any:
13
+ return tuple(int(part) for part in code.split("-"))
14
+
15
+ def escape_for_sphinx(text: str) -> str:
16
+ """
17
+ Scapes placeholders for Sphinx formatting.
18
+ """
19
+ placeholders = ["{type}", "{type_}", "{format}", "{format_}", "{param}", "{op}", "{value}"]
20
+ for ph in placeholders:
21
+ text = text.replace(ph, f"``{ph}``")
22
+ return text
23
+
24
+ path = Path(file_path)
25
+ lines = []
26
+ lines.append("Error Messages")
27
+ lines.append("################")
28
+ lines.append("")
29
+ lines.append(
30
+ "This document provides a complete list of all error messages generated by the VTL engine. "
31
+ "Each entry includes the error code, the corresponding message, and a brief description "
32
+ "to help users to understand the cause of the issue."
33
+ )
34
+ lines.append("")
35
+ lines.append("The following legend explains the error code patterns used in the VTL engine:")
36
+ lines.append("")
37
+ category_header = "Category"
38
+ pattern_header = "Pattern"
39
+ legend_rows = [
40
+ ("INPUT ERRORS", "0-1-X-X = Input Validation Errors\n"),
41
+ ("", "0-2-X-X = JSON Schema Errors\n"),
42
+ ("", "0-3-X-X = DataLoad Errors"),
43
+ ("SEMANTIC ERRORS", "1-1-X-X = Operators Semantic Errors\n"),
44
+ ("", "1-2-X-X = Semantic Analyzer Errors\n"),
45
+ ("", "1-3-X-X = AST Errors"),
46
+ ("RUNTIME ERRORS", "2-X-X-X = RunTime Operator Errors"),
47
+ ]
48
+
49
+ max_cat = max(len(category_header), max(len(row[0]) for row in legend_rows))
50
+ max_pat = max(len(pattern_header), max(len(row[1]) for row in legend_rows))
51
+ sep_legend = f"{'=' * max_cat} {'=' * max_pat}"
52
+
53
+ lines.append(sep_legend)
54
+ lines.append(f"{category_header.ljust(max_cat)} {pattern_header.ljust(max_pat)}")
55
+ lines.append(sep_legend)
56
+ for cat, pat in legend_rows:
57
+ lines.append(f"{cat.ljust(max_cat)} {pat.ljust(max_pat)}")
58
+ lines.append(sep_legend)
59
+ lines.append("")
60
+ lines.append("The following table contains all available error codes:")
61
+ lines.append("")
62
+ headers = ["Code", "Message", "Description"]
63
+ max_lengths = [len(h) for h in headers]
64
+
65
+ for code, info in messages.items():
66
+ if isinstance(info, dict):
67
+ message = escape_for_sphinx(info.get("message", ""))
68
+ description = escape_for_sphinx(info.get("description", ""))
69
+ else:
70
+ message = escape_for_sphinx(str(info))
71
+ description = ""
72
+ max_lengths[0] = max(max_lengths[0], len(code))
73
+ max_lengths[1] = max(max_lengths[1], len(message))
74
+ max_lengths[2] = max(max_lengths[2], len(description))
75
+
76
+ sep = " ".join("=" * length for length in max_lengths)
77
+ lines.append(sep)
78
+ header_line = " ".join(h.ljust(max_lengths[i]) for i, h in enumerate(headers))
79
+ lines.append(header_line)
80
+ lines.append(sep)
81
+
82
+ for code in sorted(messages.keys(), key=sort_key):
83
+ info = messages[code]
84
+ if isinstance(info, dict):
85
+ message = escape_for_sphinx(info.get("message", ""))
86
+ description = escape_for_sphinx(info.get("description", ""))
87
+ else:
88
+ message = escape_for_sphinx(str(info))
89
+ description = ""
90
+ row = " ".join(s.ljust(max_lengths[i]) for i, s in enumerate([code, message, description]))
91
+ lines.append(row)
92
+
93
+ lines.append(sep)
94
+
95
+ path.write_text("\n".join(lines), encoding="utf-8")
96
+ print(f"RST generated in {path}")
@@ -0,0 +1,159 @@
1
+ """
2
+ Exceptions.exceptions.py
3
+ ========================
4
+
5
+ Description
6
+ -----------
7
+ All exceptions exposed by the Vtl engine.
8
+ """
9
+
10
+ from typing import Any, List, Optional
11
+
12
+ from vtlengine.Exceptions.messages import centralised_messages
13
+
14
+ dataset_output = None
15
+
16
+
17
+ class VTLEngineException(Exception):
18
+ """Base class for exceptions in this module."""
19
+
20
+ def __init__(
21
+ self,
22
+ message: str,
23
+ lino: Optional[str] = None,
24
+ colno: Optional[str] = None,
25
+ code: Optional[str] = None,
26
+ ) -> None:
27
+ if code is not None:
28
+ super().__init__(message, code)
29
+ else:
30
+ super().__init__(message)
31
+ self.lino = lino
32
+ self.colno = colno
33
+
34
+ @property
35
+ def pos(self) -> List[Optional[str]]:
36
+ """ """
37
+
38
+ return [self.lino, self.colno]
39
+
40
+
41
+ class SemanticError(VTLEngineException):
42
+ """ """
43
+
44
+ output_message = " Please check transformation with output Dataset "
45
+ comp_code = None
46
+
47
+ def __init__(self, code: str, comp_code: Optional[str] = None, **kwargs: Any) -> None:
48
+ if dataset_output:
49
+ message = (
50
+ centralised_messages[code]["message"].format(**kwargs)
51
+ + self.output_message
52
+ + str(dataset_output)
53
+ )
54
+ else:
55
+ message = centralised_messages[code]["message"].format(**kwargs) # type: ignore[index]
56
+
57
+ super().__init__(message, None, None, code)
58
+
59
+ if comp_code:
60
+ self.comp_code = comp_code
61
+
62
+
63
+ class RunTimeError(VTLEngineException):
64
+ output_message = " Please check transformation with output Dataset "
65
+ comp_code = None
66
+
67
+ def __init__(
68
+ self,
69
+ code: str,
70
+ comp_code: Optional[str] = None,
71
+ **kwargs: Any,
72
+ ) -> None:
73
+ message = centralised_messages[code]["message"].format(**kwargs) # type: ignore[index]
74
+ if dataset_output:
75
+ message += self.output_message + str(dataset_output)
76
+
77
+ super().__init__(message, None, None, code)
78
+
79
+ if comp_code:
80
+ self.comp_code = comp_code
81
+
82
+
83
+ class InputValidationException(VTLEngineException):
84
+ """ """
85
+
86
+ def __init__(
87
+ self,
88
+ message: str = "default_value",
89
+ lino: Optional[str] = None,
90
+ colno: Optional[str] = None,
91
+ code: Optional[str] = None,
92
+ **kwargs: Any,
93
+ ) -> None:
94
+ if code is not None:
95
+ message = centralised_messages[code]["message"].format(**kwargs) # type: ignore[index]
96
+ super().__init__(message, lino, colno, code)
97
+ else:
98
+ super().__init__(message, lino, colno)
99
+
100
+
101
+ def check_key(field: str, dict_keys: Any, key: str) -> None:
102
+ if key not in dict_keys:
103
+ closest_key = find_closest_key(dict_keys, key)
104
+ message_append = f". Did you mean {closest_key}?" if closest_key else ""
105
+ raise SemanticError("0-1-1-13", field=field, key=key, closest_key=message_append)
106
+
107
+
108
+ def find_closest_key(dict_keys: Any, key: str) -> Optional[str]:
109
+ closest_key = None
110
+ max_distance = 3
111
+ min_distance = float("inf")
112
+
113
+ for dict_key in dict_keys:
114
+ distance = key_distance(key, dict_key)
115
+ if distance < min_distance:
116
+ min_distance = distance
117
+ closest_key = dict_key
118
+
119
+ if min_distance <= max_distance:
120
+ return closest_key
121
+ return None
122
+
123
+
124
+ def key_distance(key: str, objetive: str) -> int:
125
+ dp = [[0] * (len(objetive) + 1) for _ in range(len(key) + 1)]
126
+
127
+ for i in range(len(key) + 1):
128
+ dp[i][0] = i
129
+ for j in range(len(objetive) + 1):
130
+ dp[0][j] = j
131
+
132
+ for i in range(1, len(key) + 1):
133
+ for j in range(1, len(objetive) + 1):
134
+ cost = 0 if key[i - 1] == objetive[j - 1] else 1
135
+ dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost)
136
+
137
+ return dp[-1][-1]
138
+
139
+
140
+ class DataLoadError(VTLEngineException):
141
+ output_message = " Please check loaded file"
142
+ comp_code: Optional[str] = None
143
+
144
+ def __init__(
145
+ self,
146
+ code: str,
147
+ comp_code: Optional[str] = None,
148
+ **kwargs: Any,
149
+ ) -> None:
150
+ message = centralised_messages[code]["message"].format(**kwargs) # type: ignore[index]
151
+ if dataset_output:
152
+ message += self.output_message + " " + str(dataset_output)
153
+ else:
154
+ message += self.output_message
155
+
156
+ super().__init__(message, None, None, code)
157
+
158
+ if comp_code:
159
+ self.comp_code = comp_code