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.

Files changed (48) hide show
  1. vtlengine/API/_InternalApi.py +64 -58
  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/__extras_check.py +17 -0
  38. vtlengine/files/output/__init__.py +2 -1
  39. vtlengine/files/output/_time_period_representation.py +2 -1
  40. vtlengine/files/parser/__init__.py +47 -31
  41. vtlengine/files/parser/_rfc_dialect.py +1 -1
  42. vtlengine/files/parser/_time_checking.py +4 -4
  43. {vtlengine-1.0.3rc3.dist-info → vtlengine-1.1rc1.dist-info}/METADATA +17 -17
  44. vtlengine-1.1rc1.dist-info/RECORD +59 -0
  45. {vtlengine-1.0.3rc3.dist-info → vtlengine-1.1rc1.dist-info}/WHEEL +1 -1
  46. vtlengine/DataTypes/NumericTypesHandling.py +0 -38
  47. vtlengine-1.0.3rc3.dist-info/RECORD +0 -58
  48. {vtlengine-1.0.3rc3.dist-info → vtlengine-1.1rc1.dist-info}/LICENSE.md +0 -0
@@ -34,43 +34,43 @@ class Cast(Operator.Unary):
34
34
  # CASTS VALUES
35
35
  # Converts the value from one type to another in a way that is according to the mask
36
36
  @classmethod
37
- def cast_string_to_number(cls, *args: Any) -> Any:
37
+ def cast_string_to_number(cls, value: Any, mask: str) -> Any:
38
38
  """
39
39
  This method casts a string to a number, according to the mask.
40
40
 
41
41
  """
42
42
 
43
- raise NotImplementedError("How this cast should be implemented is not yet defined.")
43
+ raise NotImplementedError("How this mask should be implemented is not yet defined.")
44
44
 
45
45
  @classmethod
46
- def cast_string_to_date(cls, *args: Any) -> Any:
46
+ def cast_string_to_date(cls, value: Any, mask: str) -> Any:
47
47
  """
48
48
  This method casts a string to a number, according to the mask.
49
49
 
50
50
  """
51
51
 
52
- raise NotImplementedError("How this cast should be implemented is not yet defined.")
52
+ raise NotImplementedError("How this mask should be implemented is not yet defined.")
53
53
 
54
54
  @classmethod
55
- def cast_string_to_duration(cls, *args: Any) -> Any:
55
+ def cast_string_to_duration(cls, value: Any, mask: str) -> Any:
56
56
  """
57
57
  This method casts a string to a duration, according to the mask.
58
58
 
59
59
  """
60
60
 
61
- raise NotImplementedError("How this cast should be implemented is not yet defined.")
61
+ raise NotImplementedError("How this mask should be implemented is not yet defined.")
62
62
 
63
63
  @classmethod
64
- def cast_string_to_time_period(cls, *args: Any) -> Any:
64
+ def cast_string_to_time_period(cls, value: Any, mask: str) -> Any:
65
65
  """
66
66
  This method casts a string to a time period, according to the mask.
67
67
 
68
68
  """
69
69
 
70
- raise NotImplementedError("How this cast should be implemented is not yet defined.")
70
+ raise NotImplementedError("How this mask should be implemented is not yet defined.")
71
71
 
72
72
  @classmethod
73
- def cast_string_to_time(cls, *args: Any) -> Any:
73
+ def cast_string_to_time(cls, value: Any, mask: str) -> Any:
74
74
  """
75
75
  This method casts a string to a time, according to the mask.
76
76
 
@@ -78,20 +78,21 @@ class Cast(Operator.Unary):
78
78
 
79
79
  raise NotImplementedError("How this cast should be implemented is not yet defined.")
80
80
 
81
- @classmethod
82
- def cast_date_to_string(cls, *args: Any) -> Any:
83
- """ """
84
- return NotImplementedError("How this cast should be implemented is not yet defined.")
85
-
86
- @classmethod
87
- def cast_duration_to_string(cls, *args: Any) -> Any:
88
- """ """
89
- return NotImplementedError("How this cast should be implemented is not yet defined.")
90
-
91
- @classmethod
92
- def cast_time_to_string(cls, *args: Any) -> Any:
93
- """ """
94
- return NotImplementedError("How this cast should be implemented is not yet defined.")
81
+ #
82
+ # @classmethod
83
+ # def cast_date_to_string(cls, value: Any, mask: str) -> Any:
84
+ # """ """
85
+ # return NotImplementedError("How this cast should be implemented is not yet defined.")
86
+ #
87
+ # @classmethod
88
+ # def cast_duration_to_string(cls, value: Any, mask: str) -> Any:
89
+ # """ """
90
+ # return NotImplementedError("How this cast should be implemented is not yet defined.")
91
+ #
92
+ # @classmethod
93
+ # def cast_time_to_string(cls, value: Any, mask: str) -> Any:
94
+ # """ """
95
+ # return NotImplementedError("How this cast should be implemented is not yet defined.")
95
96
 
96
97
  @classmethod
97
98
  def cast_time_period_to_date(cls, value: Any, mask_value: str) -> Any:
@@ -142,7 +143,6 @@ class Cast(Operator.Unary):
142
143
 
143
144
  @classmethod
144
145
  def check_mask_value_from_time_period_to_date(cls, mask_value: str) -> None:
145
-
146
146
  if mask_value not in ["START", "END"]:
147
147
  raise SemanticError("1-1-5-4", op=cls.op, type_1="Time_Period", type_2="Date")
148
148
 
@@ -180,9 +180,11 @@ class Cast(Operator.Unary):
180
180
 
181
181
  @classmethod
182
182
  def check_cast(
183
- cls, from_type: Type[ScalarType], to_type: Type[ScalarType], mask_value: Optional[str]
183
+ cls,
184
+ from_type: Type[ScalarType],
185
+ to_type: Type[ScalarType],
186
+ mask_value: Optional[str],
184
187
  ) -> None:
185
-
186
188
  if mask_value is not None:
187
189
  cls.check_with_mask(from_type, to_type, mask_value)
188
190
  else:
@@ -192,7 +194,6 @@ class Cast(Operator.Unary):
192
194
  def check_with_mask(
193
195
  cls, from_type: Type[ScalarType], to_type: Type[ScalarType], mask_value: str
194
196
  ) -> None:
195
-
196
197
  explicit_promotion = EXPLICIT_WITH_MASK_TYPE_PROMOTION_MAPPING[from_type]
197
198
  if to_type.is_included(explicit_promotion):
198
199
  return cls.check_mask_value(from_type, to_type, mask_value)
@@ -207,7 +208,6 @@ class Cast(Operator.Unary):
207
208
 
208
209
  @classmethod
209
210
  def check_without_mask(cls, from_type: Type[ScalarType], to_type: Type[ScalarType]) -> None:
210
-
211
211
  explicit_promotion = EXPLICIT_WITHOUT_MASK_TYPE_PROMOTION_MAPPING[from_type]
212
212
  implicit_promotion = IMPLICIT_TYPE_PROMOTION_MAPPING[from_type]
213
213
  if not (to_type.is_included(explicit_promotion) or to_type.is_included(implicit_promotion)):
@@ -231,7 +231,7 @@ class Cast(Operator.Unary):
231
231
  cls, data: Any, from_type: Type[ScalarType], to_type: Type[ScalarType]
232
232
  ) -> Any:
233
233
  """
234
- cast the component to the type to_type without mask
234
+ Cast the component to the type to_type without mask
235
235
  """
236
236
 
237
237
  if to_type.is_included(IMPLICIT_TYPE_PROMOTION_MAPPING[from_type]):
@@ -242,15 +242,17 @@ class Cast(Operator.Unary):
242
242
 
243
243
  @classmethod
244
244
  def cast_mask_component(cls, data: Any, from_type: Any, to_type: Any, mask: str) -> Any:
245
-
246
245
  result = data.map(lambda x: cls.cast_value(x, from_type, to_type, mask), na_action="ignore")
247
246
  return result
248
247
 
249
248
  @classmethod
250
249
  def cast_value(
251
- cls, value: Any, provided_type: Type[ScalarType], to_type: Type[ScalarType], mask_value: str
250
+ cls,
251
+ value: Any,
252
+ provided_type: Type[ScalarType],
253
+ to_type: Type[ScalarType],
254
+ mask_value: str,
252
255
  ) -> Any:
253
-
254
256
  if provided_type == String and to_type == Number:
255
257
  return cls.cast_string_to_number(value, mask_value)
256
258
  if provided_type == String and to_type == Date:
@@ -261,12 +263,12 @@ class Cast(Operator.Unary):
261
263
  return cls.cast_string_to_time_period(value, mask_value)
262
264
  if provided_type == String and to_type == TimeInterval:
263
265
  return cls.cast_string_to_time(value, mask_value)
264
- if provided_type == Date and to_type == String:
265
- return cls.cast_date_to_string(value, mask_value)
266
- if provided_type == Duration and to_type == String:
267
- return cls.cast_duration_to_string(value, mask_value)
268
- if provided_type == TimeInterval and to_type == String:
269
- return cls.cast_time_to_string(value, mask_value)
266
+ # if provided_type == Date and to_type == String:
267
+ # return cls.cast_date_to_string(value, mask_value)
268
+ # if provided_type == Duration and to_type == String:
269
+ # return cls.cast_duration_to_string(value, mask_value)
270
+ # if provided_type == TimeInterval and to_type == String:
271
+ # return cls.cast_time_to_string(value, mask_value)
270
272
  if provided_type == TimePeriod and to_type == Date:
271
273
  return cls.cast_time_period_to_date(value, mask_value)
272
274
 
@@ -285,7 +287,6 @@ class Cast(Operator.Unary):
285
287
  scalarType: Type[ScalarType],
286
288
  mask: Optional[str] = None,
287
289
  ) -> Any:
288
-
289
290
  if mask is not None and not isinstance(mask, str):
290
291
  raise Exception(f"{cls.op} mask must be a string")
291
292
 
@@ -325,7 +326,10 @@ class Cast(Operator.Unary):
325
326
  else:
326
327
  measure_name = measure.name
327
328
  result_components[measure_name] = Component(
328
- name=measure_name, data_type=to_type, role=Role.MEASURE, nullable=measure.nullable
329
+ name=measure_name,
330
+ data_type=to_type,
331
+ role=Role.MEASURE,
332
+ nullable=measure.nullable,
329
333
  )
330
334
  return Dataset(name="result", components=result_components, data=None)
331
335
 
@@ -366,7 +370,6 @@ class Cast(Operator.Unary):
366
370
  scalarType: Type[ScalarType],
367
371
  mask: Optional[str] = None,
368
372
  ) -> Any:
369
-
370
373
  if isinstance(operand, Dataset):
371
374
  return cls.dataset_evaluation(operand, scalarType, mask)
372
375
  if isinstance(operand, Scalar):
@@ -381,7 +384,6 @@ class Cast(Operator.Unary):
381
384
  to_type: Type[ScalarType],
382
385
  mask: Optional[str] = None,
383
386
  ) -> Dataset:
384
-
385
387
  from_type = operand.get_measures()[0].data_type
386
388
  original_measure = operand.get_measures()[0]
387
389
  result_dataset = cls.dataset_validation(operand, to_type, mask)
@@ -410,7 +412,6 @@ class Cast(Operator.Unary):
410
412
  to_type: Type[ScalarType],
411
413
  mask: Optional[str] = None,
412
414
  ) -> Scalar:
413
-
414
415
  from_type = operand.data_type
415
416
  result_scalar = cls.scalar_validation(operand, to_type, mask)
416
417
  if pd.isna(operand.value):
@@ -431,7 +432,6 @@ class Cast(Operator.Unary):
431
432
  to_type: Type[ScalarType],
432
433
  mask: Optional[str] = None,
433
434
  ) -> DataComponent:
434
-
435
435
  from_type = operand.data_type
436
436
  result_component = cls.component_validation(operand, to_type, mask)
437
437
  if mask:
@@ -22,12 +22,10 @@ class Calc(Operator):
22
22
 
23
23
  @classmethod
24
24
  def validate(cls, operands: List[Union[DataComponent, Scalar]], dataset: Dataset) -> Dataset:
25
-
26
25
  result_components = {name: copy(comp) for name, comp in dataset.components.items()}
27
26
  result_dataset = Dataset(name=dataset.name, components=result_components, data=None)
28
27
 
29
28
  for operand in operands:
30
-
31
29
  if operand.name in result_dataset.components:
32
30
  if result_dataset.components[operand.name].role == Role.IDENTIFIER:
33
31
  raise SemanticError("1-1-6-13", op=cls.op, comp_name=operand.name)
@@ -72,7 +70,6 @@ class Aggregate(Operator):
72
70
 
73
71
  @classmethod
74
72
  def validate(cls, operands: List[Union[DataComponent, Scalar]], dataset: Dataset) -> Dataset:
75
-
76
73
  result_dataset = Dataset(name=dataset.name, components=dataset.components, data=None)
77
74
 
78
75
  for operand in operands:
@@ -121,7 +118,6 @@ class Aggregate(Operator):
121
118
 
122
119
 
123
120
  class Filter(Operator):
124
-
125
121
  @classmethod
126
122
  def validate(cls, condition: DataComponent, dataset: Dataset) -> Dataset:
127
123
  if condition.data_type != Boolean:
@@ -212,11 +208,17 @@ class Rename(Operator):
212
208
  for operand in operands:
213
209
  if operand.old_name not in dataset.components:
214
210
  raise SemanticError(
215
- "1-1-1-10", op=cls.op, comp_name=operand.old_name, dataset_name=dataset.name
211
+ "1-1-1-10",
212
+ op=cls.op,
213
+ comp_name=operand.old_name,
214
+ dataset_name=dataset.name,
216
215
  )
217
216
  if operand.new_name in dataset.components:
218
217
  raise SemanticError(
219
- "1-1-6-8", op=cls.op, comp_name=operand.new_name, dataset_name=dataset.name
218
+ "1-1-6-8",
219
+ op=cls.op,
220
+ comp_name=operand.new_name,
221
+ dataset_name=dataset.name,
220
222
  )
221
223
 
222
224
  result_components = {comp.name: comp for comp in dataset.components.values()}
@@ -242,7 +244,6 @@ class Rename(Operator):
242
244
 
243
245
 
244
246
  class Pivot(Operator):
245
-
246
247
  @classmethod
247
248
  def validate(cls, operands: List[str], dataset: Dataset) -> Dataset:
248
249
  raise NotImplementedError
@@ -253,7 +254,6 @@ class Pivot(Operator):
253
254
 
254
255
 
255
256
  class Unpivot(Operator):
256
-
257
257
  @classmethod
258
258
  def validate(cls, operands: List[str], dataset: Dataset) -> Dataset:
259
259
  if len(operands) != 2:
@@ -311,11 +311,17 @@ class Sub(Operator):
311
311
  for operand in operands:
312
312
  if operand.name not in dataset.components:
313
313
  raise SemanticError(
314
- "1-1-1-10", op=cls.op, comp_name=operand.name, dataset_name=dataset.name
314
+ "1-1-1-10",
315
+ op=cls.op,
316
+ comp_name=operand.name,
317
+ dataset_name=dataset.name,
315
318
  )
316
319
  if operand.role != Role.IDENTIFIER:
317
320
  raise SemanticError(
318
- "1-1-6-10", op=cls.op, operand=operand.name, dataset_name=dataset.name
321
+ "1-1-6-10",
322
+ op=cls.op,
323
+ operand=operand.name,
324
+ dataset_name=dataset.name,
319
325
  )
320
326
  if isinstance(operand, Scalar):
321
327
  raise SemanticError("1-1-6-5", op=cls.op, name=operand.name)
@@ -74,10 +74,11 @@ class Binary(Operator.Binary):
74
74
  return_type = Boolean
75
75
 
76
76
  @classmethod
77
- def _cast_values(cls,
78
- x: Optional[Union[int, float, str, bool]],
79
- y: Optional[Union[int, float, str, bool]]
80
- ) -> Any:
77
+ def _cast_values(
78
+ cls,
79
+ x: Optional[Union[int, float, str, bool]],
80
+ y: Optional[Union[int, float, str, bool]],
81
+ ) -> Any:
81
82
  # Cast values to compatible types for comparison
82
83
  try:
83
84
  if isinstance(x, str) and isinstance(y, bool):
@@ -247,9 +248,7 @@ class Between(Operator.Operator):
247
248
  z: Optional[Union[int, float, bool, str]],
248
249
  ) -> Optional[bool]:
249
250
  return (
250
- None
251
- if (pd.isnull(x) or pd.isnull(y) or pd.isnull(z))
252
- else y <= x <= z # type: ignore[operator]
251
+ None if (pd.isnull(x) or pd.isnull(y) or pd.isnull(z)) else y <= x <= z # type: ignore[operator]
253
252
  )
254
253
 
255
254
  @classmethod
@@ -264,7 +263,8 @@ class Between(Operator.Operator):
264
263
  to_data = pd.Series(to_data, index=series.index)
265
264
  df = pd.DataFrame({"operand": series, "from_data": from_data, "to_data": to_data})
266
265
  return df.apply(
267
- lambda x: cls.op_func(x["operand"], x["from_data"], x["to_data"]), axis=1
266
+ lambda x: cls.op_func(x["operand"], x["from_data"], x["to_data"]),
267
+ axis=1,
268
268
  )
269
269
 
270
270
  return series.map(lambda x: cls.op_func(x, from_data, to_data))
@@ -310,13 +310,19 @@ class Between(Operator.Operator):
310
310
  result = Dataset(name=operand.name, components=result_components, data=None)
311
311
  elif isinstance(operand, DataComponent):
312
312
  result = DataComponent(
313
- name=operand.name, data=None, data_type=cls.return_type, role=operand.role
313
+ name=operand.name,
314
+ data=None,
315
+ data_type=cls.return_type,
316
+ role=operand.role,
314
317
  )
315
318
  elif isinstance(from_, Scalar) and isinstance(to, Scalar):
316
319
  result = Scalar(name=operand.name, value=None, data_type=cls.return_type)
317
320
  else: # From or To is a DataComponent, or both
318
321
  result = DataComponent(
319
- name=operand.name, data=None, data_type=cls.return_type, role=Role.MEASURE
322
+ name=operand.name,
323
+ data=None,
324
+ data_type=cls.return_type,
325
+ role=Role.MEASURE,
320
326
  )
321
327
 
322
328
  if isinstance(operand, Dataset):
@@ -369,14 +375,16 @@ class Between(Operator.Operator):
369
375
  elif isinstance(operand, Scalar) and (
370
376
  isinstance(from_data, pd.Series) or isinstance(to_data, pd.Series)
371
377
  ): # From or To is a DataComponent, or both
372
-
373
378
  if isinstance(from_data, pd.Series):
374
379
  series = pd.Series(operand.value, index=from_data.index, dtype=object)
375
380
  elif isinstance(to_data, pd.Series):
376
381
  series = pd.Series(operand.value, index=to_data.index, dtype=object)
377
382
  result_series = cls.apply_operation_component(series, from_data, to_data)
378
383
  result = DataComponent(
379
- name=operand.name, data=result_series, data_type=cls.return_type, role=Role.MEASURE
384
+ name=operand.name,
385
+ data=result_series,
386
+ data_type=cls.return_type,
387
+ role=Role.MEASURE,
380
388
  )
381
389
  return result
382
390
 
@@ -39,7 +39,7 @@ class If(Operator):
39
39
  validate: Class method that has two branches so datacomponent and datasets can be validated. With datacomponent,
40
40
  the code reviews if it is actually a Measure and if it is a binary operation. Dataset branch reviews if the
41
41
  identifiers are the same in 'if', 'then' and 'else'.
42
- """ # noqa E501
42
+ """ # noqa E501
43
43
 
44
44
  @classmethod
45
45
  def evaluate(cls, condition: Any, true_branch: Any, false_branch: Any) -> Any:
@@ -66,7 +66,8 @@ class If(Operator):
66
66
  else:
67
67
  false_data = false_branch.data.reindex(condition.data.index)
68
68
  result = np.where(condition.data, true_data, false_data)
69
- return pd.Series(result, index=condition.data.index)
69
+
70
+ return pd.Series(result, index=condition.data.index) # type: ignore[union-attr]
70
71
 
71
72
  @classmethod
72
73
  def dataset_level_evaluation(
@@ -80,7 +81,11 @@ class If(Operator):
80
81
  if isinstance(true_branch, Dataset):
81
82
  if len(true_data) > 0 and true_branch.data is not None:
82
83
  true_data = pd.merge(
83
- true_data, true_branch.data, on=ids, how="right", suffixes=("_condition", "")
84
+ true_data,
85
+ true_branch.data,
86
+ on=ids,
87
+ how="right",
88
+ suffixes=("_condition", ""),
84
89
  )
85
90
  else:
86
91
  true_data = pd.DataFrame(columns=true_branch.get_components_names())
@@ -91,7 +96,11 @@ class If(Operator):
91
96
  if isinstance(false_branch, Dataset):
92
97
  if len(false_data) > 0 and false_branch.data is not None:
93
98
  false_data = pd.merge(
94
- false_data, false_branch.data, on=ids, how="right", suffixes=("_condition", "")
99
+ false_data,
100
+ false_branch.data,
101
+ on=ids,
102
+ how="right",
103
+ suffixes=("_condition", ""),
95
104
  )
96
105
  else:
97
106
  false_data = pd.DataFrame(columns=false_branch.get_components_names())
@@ -141,7 +150,9 @@ class If(Operator):
141
150
  if isinstance(condition, DataComponent):
142
151
  if not condition.data_type == Boolean:
143
152
  raise SemanticError(
144
- "1-1-9-11", op=cls.op, type=SCALAR_TYPES_CLASS_REVERSE[condition.data_type]
153
+ "1-1-9-11",
154
+ op=cls.op,
155
+ type=SCALAR_TYPES_CLASS_REVERSE[condition.data_type],
145
156
  )
146
157
  if not isinstance(left, Scalar) or not isinstance(right, Scalar):
147
158
  nullable = condition.nullable
@@ -191,7 +202,8 @@ class If(Operator):
191
202
  if component.data_type != right.components[component.name].data_type:
192
203
  component.data_type = right.components[component.name].data_type = (
193
204
  binary_implicit_promotion(
194
- component.data_type, right.components[component.name].data_type
205
+ component.data_type,
206
+ right.components[component.name].data_type,
195
207
  )
196
208
  )
197
209
  if isinstance(condition, Dataset):
@@ -219,7 +231,7 @@ class Nvl(Binary):
219
231
  Validate: Class method that validates if the operation at scalar,
220
232
  datacomponent or dataset level can be performed.
221
233
  Evaluate: Evaluates the actual operation, returning the result.
222
- """ # noqa E501
234
+ """ # noqa E501
223
235
 
224
236
  @classmethod
225
237
  def evaluate(cls, left: Any, right: Any) -> Union[Scalar, DataComponent, Dataset]:
@@ -290,13 +302,16 @@ class Nvl(Binary):
290
302
 
291
303
 
292
304
  class Case(Operator):
293
-
294
305
  @classmethod
295
306
  def evaluate(
296
307
  cls, conditions: List[Any], thenOps: List[Any], elseOp: Any
297
308
  ) -> Union[Scalar, DataComponent, Dataset]:
298
-
299
309
  result = cls.validate(conditions, thenOps, elseOp)
310
+ for condition in conditions:
311
+ if isinstance(condition, (DataComponent, Dataset)) and condition.data is not None:
312
+ condition.data.fillna(False, inplace=True)
313
+ elif isinstance(condition, Scalar) and condition.value is None:
314
+ condition.value = False
300
315
 
301
316
  if isinstance(result, Scalar):
302
317
  result.value = elseOp.value
@@ -309,8 +324,10 @@ class Case(Operator):
309
324
 
310
325
  for i, condition in enumerate(conditions):
311
326
  value = thenOps[i].value if isinstance(thenOps[i], Scalar) else thenOps[i].data
312
- result.data = np.where(
313
- condition.data, value, result.data # type: ignore[call-overload]
327
+ result.data = np.where( # type: ignore[call-overload]
328
+ condition.data.notna(),
329
+ np.where(condition.data, value, result.data), # type: ignore[call-overload]
330
+ result.data,
314
331
  )
315
332
 
316
333
  condition_mask_else = ~np.any([condition.data for condition in conditions], axis=0)
@@ -361,7 +378,6 @@ class Case(Operator):
361
378
  def validate(
362
379
  cls, conditions: List[Any], thenOps: List[Any], elseOp: Any
363
380
  ) -> Union[Scalar, DataComponent, Dataset]:
364
-
365
381
  if len(set(map(type, conditions))) > 1:
366
382
  raise SemanticError("2-1-9-1", op=cls.op)
367
383
 
@@ -395,9 +411,10 @@ class Case(Operator):
395
411
  raise SemanticError("2-1-9-4", op=cls.op, name=condition.name)
396
412
 
397
413
  nullable = any(
398
- thenOp.nullable if isinstance(thenOp, DataComponent) else thenOp.data_type == Null
414
+ (thenOp.nullable if isinstance(thenOp, DataComponent) else thenOp.data_type == Null)
399
415
  for thenOp in ops
400
416
  )
417
+ nullable |= any(condition.nullable for condition in conditions)
401
418
 
402
419
  data_type = ops[0].data_type
403
420
  for op in ops[1:]:
@@ -23,7 +23,10 @@ class Membership(Binary):
23
23
  def validate(cls, left_operand: Any, right_operand: Any) -> Dataset:
24
24
  if right_operand not in left_operand.components:
25
25
  raise SemanticError(
26
- "1-1-1-10", op=cls.op, comp_name=right_operand, dataset_name=left_operand.name
26
+ "1-1-1-10",
27
+ op=cls.op,
28
+ comp_name=right_operand,
29
+ dataset_name=left_operand.name,
27
30
  )
28
31
 
29
32
  component = left_operand.components[right_operand]
@@ -48,7 +51,10 @@ class Membership(Binary):
48
51
 
49
52
  @classmethod
50
53
  def evaluate(
51
- cls, left_operand: Dataset, right_operand: str, is_from_component_assignment: bool = False
54
+ cls,
55
+ left_operand: Dataset,
56
+ right_operand: str,
57
+ is_from_component_assignment: bool = False,
52
58
  ) -> Union[DataComponent, Dataset]:
53
59
  result_dataset = cls.validate(left_operand, right_operand)
54
60
  if left_operand.data is not None:
@@ -128,12 +134,11 @@ class Eval(Unary):
128
134
  external_routine: ExternalRoutine,
129
135
  output: Dataset,
130
136
  ) -> Dataset:
131
-
132
137
  empty_data_dict = {}
133
138
  for ds_name in external_routine.dataset_names:
134
139
  if ds_name not in operands:
135
140
  raise ValueError(
136
- f"External Routine dataset {ds_name} " f"is not present in Eval operands"
141
+ f"External Routine dataset {ds_name} is not present in Eval operands"
137
142
  )
138
143
  empty_data = pd.DataFrame(
139
144
  columns=[comp.name for comp in operands[ds_name].components.values()]
@@ -24,12 +24,9 @@ def get_measure_from_dataset(dataset: Dataset, code_item: str) -> DataComponent:
24
24
 
25
25
 
26
26
  class HRComparison(Operators.Binary):
27
-
28
27
  @classmethod
29
28
  def imbalance_func(cls, x: Any, y: Any) -> Any:
30
- if pd.isnull(x) or pd.isnull(y):
31
- return None
32
- return x - y
29
+ return None if pd.isnull(x) or pd.isnull(y) else x - y
33
30
 
34
31
  @staticmethod
35
32
  def hr_func(left_series: Any, right_series: Any, hr_mode: str) -> Any:
@@ -44,8 +41,8 @@ class HRComparison(Operators.Binary):
44
41
  result[mask_remove] = "REMOVE_VALUE"
45
42
  result[mask_null] = None
46
43
  elif hr_mode == "non_null":
47
- mask_remove = left_series.isnull() | right_series.isnull()
48
- result[mask_remove] = "REMOVE_VALUE"
44
+ mask_remove = left_series.isnull() | right_series.isnull()
45
+ result[mask_remove] = "REMOVE_VALUE"
49
46
  elif hr_mode == "non_zero":
50
47
  mask_remove = (left_series == 0) & (right_series == 0)
51
48
  result[mask_remove] = "REMOVE_VALUE"
@@ -66,11 +63,7 @@ class HRComparison(Operators.Binary):
66
63
  return result
67
64
 
68
65
  @classmethod
69
- def validate(cls,
70
- left_operand: Dataset,
71
- right_operand: DataComponent,
72
- hr_mode: str
73
- ) -> Dataset:
66
+ def validate(cls, left_operand: Dataset, right_operand: DataComponent, hr_mode: str) -> Dataset:
74
67
  result_components = {
75
68
  comp_name: copy(comp)
76
69
  for comp_name, comp in left_operand.components.items()
@@ -136,7 +129,6 @@ class HRLessEqual(HRComparison):
136
129
 
137
130
 
138
131
  class HRBinNumeric(Operators.Binary):
139
-
140
132
  @classmethod
141
133
  def op_func(cls, x: Any, y: Any) -> Any:
142
134
  if not pd.isnull(x) and x == "REMOVE_VALUE":
@@ -166,7 +158,6 @@ class HRBinMinus(HRBinNumeric):
166
158
 
167
159
 
168
160
  class HRUnNumeric(Operators.Unary):
169
-
170
161
  @classmethod
171
162
  def evaluate(cls, operand: DataComponent) -> DataComponent: # type: ignore[override]
172
163
  result_data = cls.apply_operation_component(operand.data)
@@ -190,7 +181,6 @@ class HRUnMinus(HRUnNumeric):
190
181
 
191
182
 
192
183
  class HAAssignment(Operators.Binary):
193
-
194
184
  @classmethod
195
185
  def validate(cls, left: Dataset, right: DataComponent, hr_mode: str) -> Dataset:
196
186
  result_components = {comp_name: copy(comp) for comp_name, comp in left.components.items()}