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,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))