omnata-plugin-runtime 0.10.7a254__py3-none-any.whl → 0.10.8__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.
@@ -52,6 +52,9 @@ class JsonSchemaProperty(BaseModel):
52
52
  requiredStreamNames: Optional[List[str]] = Field(
53
53
  None, description="The names of the streams that are depended upon by this column, via joins. If these streams are not selected, the column will be omitted."
54
54
  )
55
+ referencedFields: Optional[Dict[str,List[str]]] = Field(
56
+ None, description="The names of fields that are referenced by this field, keyed on the stream name (or None if it's the current stream). This is used to order the fields, and also to cascade the removal of unsupported fields (e.g. in formulas)."
57
+ )
55
58
 
56
59
  @model_validator(mode='after')
57
60
  def validate(self) -> Self:
@@ -165,6 +168,9 @@ class SnowflakeViewColumn(BaseModel):
165
168
  required_stream_names: Optional[List[str]] = Field(
166
169
  default=None, description="The names of the streams that are depended upon by this column, via joins. If these streams are not selected, the column will be omitted"
167
170
  )
171
+ referenced_columns: Optional[Dict[str,List[str]]] = Field(
172
+ default=None, description="The names of columns that are referenced by this column, keyed on the stream name (or None if it's the current stream). This is used to order the columns, and also to cascade the removal of unsupported columns (e.g. in formulas)."
173
+ )
168
174
 
169
175
  def __repr__(self) -> str:
170
176
  return "SnowflakeViewColumn(name=%r, definition=%r, comment=%r)" % (
@@ -230,19 +236,23 @@ class SnowflakeViewColumn(BaseModel):
230
236
  if not json_schema_property.snowflakeColumnExpression:
231
237
  expression=f"""{expression}::{json_schema_property.snowflake_data_type}"""
232
238
  required_stream_names = None
239
+ referenced_columns = None
233
240
  if json_schema_property.requiredStreamNames:
234
241
  required_stream_names = json_schema_property.requiredStreamNames
242
+ if json_schema_property.referencedFields:
243
+ referenced_columns = json_schema_property.referencedFields
235
244
  return cls(
236
245
  name=final_column_name,
237
246
  original_name=column_name,
238
247
  expression=expression,
239
248
  comment=comment,
240
249
  is_join_column=json_schema_property.isJoinColumn,
241
- required_stream_names=required_stream_names
250
+ required_stream_names=required_stream_names,
251
+ referenced_columns=referenced_columns
242
252
  )
243
253
 
244
254
  @classmethod
245
- def order_by_reference(cls,columns:List[Self]) -> List[Self]:
255
+ def order_by_reference(cls,current_stream_name:str,columns:List[Self]) -> List[Self]:
246
256
  """
247
257
  In some situations, column expressions may reference the alias of another column
248
258
  This is allowed in Snowflake, as long as the aliased column is defined before it's used in a later column
@@ -262,7 +272,7 @@ class SnowflakeViewColumn(BaseModel):
262
272
  for other_column in columns:
263
273
  if column==other_column:
264
274
  continue
265
- if f'"{column.original_name}"' in other_column.expression:
275
+ if column.original_name in (other_column.referenced_columns or {}).get(current_stream_name,[]):
266
276
  if column not in columns_to_move:
267
277
  columns_to_move.append(column)
268
278
 
@@ -413,6 +423,12 @@ class SnowflakeViewPart(BaseModel):
413
423
  select {', '.join([c.definition(original_name=original_name) for c in self.direct_columns()])}
414
424
  from {self.raw_table_location.get_fully_qualified_name()}
415
425
  ) """
426
+
427
+ def columns_missing(self,columns_to_check:List[str]) -> List[str]:
428
+ """
429
+ Returns a list of columns that are missing from the view part.
430
+ """
431
+ return [c for c in columns_to_check if c not in [c.original_name for c in self.columns]]
416
432
 
417
433
  class SnowflakeViewParts(BaseModel):
418
434
  """
@@ -484,7 +500,7 @@ class SnowflakeViewParts(BaseModel):
484
500
  column_name_environment=column_name_environment,
485
501
  column_name_expression=column_name_expression
486
502
  )
487
- joined_parts = []
503
+ joined_parts:List[SnowflakeViewPart] = []
488
504
  # remove the joins from the main part if they are not in the raw stream locations
489
505
  main_stream_view_part.joins = [join for join in main_stream_view_part.joins
490
506
  if join.join_stream_name in raw_stream_locations
@@ -499,21 +515,50 @@ class SnowflakeViewParts(BaseModel):
499
515
  column_name_environment=column_name_environment,
500
516
  column_name_expression=column_name_expression
501
517
  ))
502
- # For each column, the plugin can advise which streams are required for the join, which comes through as required_stream_names
518
+ # For each column, the plugin can advise which fields (of the same stream or joined) are required for the join, which comes through as referenced_columns
503
519
  # on the SnowflakeViewColumn object.
504
- # Until this generate function is called with the raw stream names, we don't know which streams the user has actually selected.
505
- # So now there's a pruning process where we remove columns from the main view part that depend on streams that are not selected
506
- for column in main_stream_view_part.columns:
507
- if column.required_stream_names:
508
- for required_stream_name in column.required_stream_names:
509
- if required_stream_name not in raw_stream_locations:
510
- logger.warning(f"Column {column.name} in stream {stream_name} requires stream {required_stream_name} to be selected, but it was not provided")
511
- main_stream_view_part.columns.remove(column)
512
- break
520
+ # Until this generate function is called with the raw stream names, we don't know which streams the user has actually selected, nor which
521
+ # fields are actually available (some may be dropped due to something like an unsupported formula).
522
+ # So now there's a pruning process where we remove columns that reference fields that are not available.
523
+ # We'll start by doing a first pass and removing unavailable columns from other streams
524
+ # then, we can do a final pass and remove columns that reference fields that are not available in the current stream
525
+ prune_count = 0
526
+ while prune(main_stream_view_part,joined_parts):
527
+ prune_count += 1
528
+ if prune_count > 10000:
529
+ raise ValueError("Pruning of columns from the view has entered an infinite loop")
513
530
 
514
531
  return cls(main_part=main_stream_view_part, joined_parts=joined_parts)
515
532
 
516
-
533
+ def prune(view_part:SnowflakeViewPart,joined_parts:List[SnowflakeViewPart]) -> bool:
534
+ """
535
+ Prunes columns from the main view part that reference fields that are not available in the joined parts.
536
+ Returns True if columns were removed, False otherwise.
537
+ """
538
+ for column in view_part.columns:
539
+ if column.referenced_columns:
540
+ for referenced_stream_name, referenced_fields in column.referenced_columns.items():
541
+
542
+ if referenced_stream_name == view_part.stream_name:
543
+ part = view_part
544
+ else:
545
+ part = next((part for part in joined_parts if part.stream_name==referenced_stream_name),None)
546
+ if part is None:
547
+ logger.warning(f"Column {column.name} in stream {view_part.stream_name} references stream {referenced_stream_name}, but it was not provided")
548
+ view_part.columns.remove(column)
549
+ return True
550
+
551
+ columns_missing_from_join = part.columns_missing(referenced_fields)
552
+ if len(columns_missing_from_join) > 0:
553
+ logger.warning(f"Column {column.name} in stream {view_part.stream_name} references fields {columns_missing_from_join} in stream {referenced_stream_name}, but they were not provided")
554
+ view_part.columns.remove(column)
555
+ return True
556
+ else:
557
+ # no columns were removed, but we need to check if the columns that are referenced are not themselves referencing other missing columns
558
+ if part != view_part:
559
+ return prune(part,joined_parts)
560
+
561
+ return False
517
562
 
518
563
  class JsonSchemaTopLevel(BaseModel):
519
564
  """
@@ -679,7 +724,7 @@ def normalized_view_part(
679
724
  #- APP_IDENTIFIER
680
725
  #- Direct and joined columns, ordered so that columns that reference other columns are defined after the columns they reference
681
726
  #- OMNATA_RETRIEVE_DATE, OMNATA_RAW_RECORD, OMNATA_IS_DELETED, OMNATA_RUN_ID
682
- view_columns = SnowflakeViewColumn.order_by_reference(direct_view_columns +
727
+ view_columns = SnowflakeViewColumn.order_by_reference(stream_name,direct_view_columns +
683
728
  join_view_columns)
684
729
  return SnowflakeViewPart(
685
730
  stream_name=stream_name,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: omnata-plugin-runtime
3
- Version: 0.10.7a254
3
+ Version: 0.10.8
4
4
  Summary: Classes and common runtime components for building and running Omnata Plugins
5
5
  Author: James Weakley
6
6
  Author-email: james.weakley@omnata.com
@@ -2,12 +2,12 @@ omnata_plugin_runtime/__init__.py,sha256=MS9d1whnfT_B3-ThqZ7l63QeC_8OEKTuaYV5wTw
2
2
  omnata_plugin_runtime/api.py,sha256=baGraSMiD4Yvi3ZWrEv_TKh8Ktd1U8riBdOpe9j0Puw,8202
3
3
  omnata_plugin_runtime/configuration.py,sha256=0rfIGv8rCu8OwL7m-VOXIBjd05iyaBWRdt2h9o6scwo,46754
4
4
  omnata_plugin_runtime/forms.py,sha256=9YHJ_T17lT-rwyDaUg_0yj_YMPda4DRCw_wrvf8hE0E,19964
5
- omnata_plugin_runtime/json_schema.py,sha256=0R7q0cKKcca_TapF7xIibsTXcg756u_LJGvlQ_-OlDI,31128
5
+ omnata_plugin_runtime/json_schema.py,sha256=6dLkOC_aeD0iwi1neCnjcwHlqj-_IfcTlVDUr-1den0,34158
6
6
  omnata_plugin_runtime/logging.py,sha256=WBuZt8lF9E5oFWM4KYQbE8dDJ_HctJ1pN3BHwU6rcd0,4461
7
7
  omnata_plugin_runtime/omnata_plugin.py,sha256=M0b6f9lKKEoEI0zf-ZwZcIPKPQTmHTIMhvcrBc94Mhg,133278
8
8
  omnata_plugin_runtime/plugin_entrypoints.py,sha256=iqGl8_nEEnPGKg3Aem4YLSQ6d5xS3ju5gq8MJbx6sCA,31968
9
9
  omnata_plugin_runtime/rate_limiting.py,sha256=qpr5esU4Ks8hMzuMpSR3gLFdor2ZUXYWCjmsQH_K6lQ,25882
10
- omnata_plugin_runtime-0.10.7a254.dist-info/LICENSE,sha256=rGaMQG3R3F5-JGDp_-rlMKpDIkg5n0SI4kctTk8eZSI,56
11
- omnata_plugin_runtime-0.10.7a254.dist-info/METADATA,sha256=U4t4oT4Fx7nbiH0sItG-DZkluodARR0fMND_VSwrqlA,2211
12
- omnata_plugin_runtime-0.10.7a254.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
13
- omnata_plugin_runtime-0.10.7a254.dist-info/RECORD,,
10
+ omnata_plugin_runtime-0.10.8.dist-info/LICENSE,sha256=rGaMQG3R3F5-JGDp_-rlMKpDIkg5n0SI4kctTk8eZSI,56
11
+ omnata_plugin_runtime-0.10.8.dist-info/METADATA,sha256=qNG7CjTY7WINLGWk7jjkViRn0w7l8Gz-kGUrG9D22vA,2207
12
+ omnata_plugin_runtime-0.10.8.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
13
+ omnata_plugin_runtime-0.10.8.dist-info/RECORD,,