omnata-plugin-runtime 0.11.4__py3-none-any.whl → 0.11.5a322__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.
- omnata_plugin_runtime/json_schema.py +175 -78
- {omnata_plugin_runtime-0.11.4.dist-info → omnata_plugin_runtime-0.11.5a322.dist-info}/METADATA +2 -3
- {omnata_plugin_runtime-0.11.4.dist-info → omnata_plugin_runtime-0.11.5a322.dist-info}/RECORD +5 -5
- {omnata_plugin_runtime-0.11.4.dist-info → omnata_plugin_runtime-0.11.5a322.dist-info}/WHEEL +0 -0
- {omnata_plugin_runtime-0.11.4.dist-info → omnata_plugin_runtime-0.11.5a322.dist-info}/licenses/LICENSE +0 -0
|
@@ -664,9 +664,12 @@ class SnowflakeViewParts(BaseModel):
|
|
|
664
664
|
)
|
|
665
665
|
joined_parts:List[SnowflakeViewPart] = []
|
|
666
666
|
# remove the joins from the main part if they are not in the raw stream locations
|
|
667
|
+
original_join_count = len(main_stream_view_part.joins)
|
|
667
668
|
main_stream_view_part.joins = [join for join in main_stream_view_part.joins
|
|
668
669
|
if join.join_stream_name in raw_stream_locations
|
|
669
670
|
and join.join_stream_name in stream_schemas]
|
|
671
|
+
if len(main_stream_view_part.joins) < original_join_count:
|
|
672
|
+
logger.debug(f"Removed {original_join_count - len(main_stream_view_part.joins)} joins from stream: {stream_name} due to missing raw stream locations or schemas")
|
|
670
673
|
|
|
671
674
|
for join in main_stream_view_part.joins:
|
|
672
675
|
logger.debug(f"Generating view parts for join stream: {join.join_stream_name}")
|
|
@@ -679,6 +682,8 @@ class SnowflakeViewParts(BaseModel):
|
|
|
679
682
|
column_name_expression=column_name_expression,
|
|
680
683
|
plugin_app_database=plugin_app_database
|
|
681
684
|
))
|
|
685
|
+
if len(main_stream_view_part.joins) == 0:
|
|
686
|
+
logger.debug(f"No joins found for stream: {stream_name}")
|
|
682
687
|
# 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
|
|
683
688
|
# on the SnowflakeViewColumn object.
|
|
684
689
|
# Until this generate function is called with the raw stream names, we don't know which streams the user has actually selected, nor which
|
|
@@ -697,7 +702,8 @@ class SnowflakeViewParts(BaseModel):
|
|
|
697
702
|
|
|
698
703
|
# Process all joins to build the mappings
|
|
699
704
|
for part in [main_stream_view_part] + joined_parts:
|
|
700
|
-
|
|
705
|
+
joined_parts_names = [j.join_stream_name for j in part.joins]
|
|
706
|
+
logger.debug(f"Processing joins for stream: {part.stream_name} (joined streams: {joined_parts_names})")
|
|
701
707
|
# Make sure the part's stream name is in the mappings
|
|
702
708
|
if part.stream_name not in stream_to_aliases:
|
|
703
709
|
stream_to_aliases[part.stream_name] = [part.stream_name]
|
|
@@ -807,19 +813,8 @@ class SnowflakeViewParts(BaseModel):
|
|
|
807
813
|
# If we get here, no circular references were found
|
|
808
814
|
logger.debug("No circular references found")
|
|
809
815
|
|
|
810
|
-
#
|
|
811
|
-
|
|
812
|
-
# then, we can do a final pass and remove columns that reference fields that are not available in the current stream
|
|
813
|
-
|
|
814
|
-
# Now proceed with the actual pruning process
|
|
815
|
-
# First, removing unavailable columns from other streams
|
|
816
|
-
# then, we can do a final pass and remove columns that reference fields that are not available in the current stream
|
|
817
|
-
|
|
818
|
-
prune_count = 0
|
|
819
|
-
while prune(main_stream_view_part, joined_parts):
|
|
820
|
-
prune_count += 1
|
|
821
|
-
if prune_count > 10:
|
|
822
|
-
raise ValueError("Pruning of columns from the view has entered an infinite loop")
|
|
816
|
+
# Prune columns using graph-based dependency resolution (single pass)
|
|
817
|
+
prune(main_stream_view_part, joined_parts)
|
|
823
818
|
|
|
824
819
|
return cls(main_part=main_stream_view_part, joined_parts=joined_parts)
|
|
825
820
|
|
|
@@ -844,81 +839,183 @@ def find_part(view_part: SnowflakeViewPart, joined_parts: List[SnowflakeViewPart
|
|
|
844
839
|
|
|
845
840
|
def prune(view_part: SnowflakeViewPart, joined_parts: List[SnowflakeViewPart]) -> bool:
|
|
846
841
|
"""
|
|
847
|
-
Prunes columns from view parts
|
|
842
|
+
Prunes columns from view parts using graph-based dependency resolution.
|
|
848
843
|
|
|
849
|
-
|
|
850
|
-
1.
|
|
851
|
-
2.
|
|
844
|
+
Uses TopologicalSorter to:
|
|
845
|
+
1. Build a complete dependency graph of all columns across all parts
|
|
846
|
+
2. Identify "root" columns that must be kept (in main part or used in joins)
|
|
847
|
+
3. Traverse dependencies to find all transitively required columns
|
|
848
|
+
4. Remove columns that aren't needed
|
|
852
849
|
|
|
853
850
|
Returns True if any columns were removed, False otherwise.
|
|
854
|
-
Raises ValueError if a cyclic dependency is detected.
|
|
855
851
|
"""
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
852
|
+
|
|
853
|
+
all_parts = [view_part] + joined_parts
|
|
854
|
+
|
|
855
|
+
# Build column registry: (stream_name, column_name) -> column object
|
|
856
|
+
all_columns: Dict[Tuple[str, str], SnowflakeViewColumn] = {}
|
|
857
|
+
for part in all_parts:
|
|
858
|
+
for column in part.columns:
|
|
859
|
+
all_columns[(part.stream_name, column.original_name)] = column
|
|
860
|
+
|
|
861
|
+
# Build dependency graph for topological analysis
|
|
862
|
+
# Key: (stream, column), Value: list of (stream, column) dependencies
|
|
863
|
+
# Also track columns with invalid dependencies (reference non-existent columns)
|
|
864
|
+
dependency_graph: Dict[Tuple[str, str], List[Tuple[str, str]]] = {}
|
|
865
|
+
columns_with_invalid_deps: set[Tuple[str, str]] = set()
|
|
866
|
+
|
|
867
|
+
# First pass: build dependency graph and detect direct invalid references
|
|
868
|
+
for part in all_parts:
|
|
869
|
+
for column in part.columns:
|
|
870
|
+
key = (part.stream_name, column.original_name)
|
|
871
|
+
deps = []
|
|
872
|
+
has_invalid_dep = False
|
|
867
873
|
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
874
|
+
if column.referenced_columns:
|
|
875
|
+
for ref_stream_name, ref_fields in column.referenced_columns.items():
|
|
876
|
+
# Resolve stream alias to actual stream name
|
|
877
|
+
resolved_stream = ref_stream_name
|
|
878
|
+
for join in view_part.joins:
|
|
879
|
+
if join.join_stream_alias == ref_stream_name:
|
|
880
|
+
resolved_stream = join.join_stream_name
|
|
881
|
+
break
|
|
882
|
+
|
|
883
|
+
for ref_field in ref_fields:
|
|
884
|
+
dep_key = (resolved_stream, ref_field)
|
|
885
|
+
if dep_key in all_columns:
|
|
886
|
+
deps.append(dep_key)
|
|
887
|
+
else:
|
|
888
|
+
logger.warning(
|
|
889
|
+
f"Column {column.original_name} in {part.stream_name} references "
|
|
890
|
+
f"{ref_field} in {resolved_stream}, which doesn't exist"
|
|
891
|
+
)
|
|
892
|
+
has_invalid_dep = True
|
|
872
893
|
|
|
873
|
-
|
|
874
|
-
if
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
894
|
+
dependency_graph[key] = deps
|
|
895
|
+
if has_invalid_dep:
|
|
896
|
+
columns_with_invalid_deps.add(key)
|
|
897
|
+
|
|
898
|
+
# Second pass: propagate invalidity to columns that depend on invalid columns
|
|
899
|
+
# Keep iterating until no new invalid columns are found
|
|
900
|
+
changed = True
|
|
901
|
+
while changed:
|
|
902
|
+
changed = False
|
|
903
|
+
for col_key, deps in dependency_graph.items():
|
|
904
|
+
if col_key not in columns_with_invalid_deps:
|
|
905
|
+
# Check if any dependency is invalid
|
|
906
|
+
for dep_key in deps:
|
|
907
|
+
if dep_key in columns_with_invalid_deps:
|
|
908
|
+
logger.warning(
|
|
909
|
+
f"Column {col_key[1]} in {col_key[0]} depends on "
|
|
910
|
+
f"{dep_key[1]} in {dep_key[0]}, which has invalid dependencies"
|
|
911
|
+
)
|
|
912
|
+
columns_with_invalid_deps.add(col_key)
|
|
913
|
+
changed = True
|
|
914
|
+
break
|
|
915
|
+
|
|
916
|
+
# Build alias to stream mapping
|
|
917
|
+
alias_to_stream: Dict[str, str] = {}
|
|
918
|
+
for part in all_parts:
|
|
919
|
+
alias_to_stream[part.stream_name] = part.stream_name
|
|
920
|
+
for join in part.joins:
|
|
921
|
+
alias_to_stream[join.join_stream_alias] = join.join_stream_name
|
|
922
|
+
# left_alias might be an alias for a joined stream, resolve it
|
|
923
|
+
if join.left_alias not in alias_to_stream:
|
|
924
|
+
# Try to find the stream for this alias
|
|
925
|
+
for other_part in all_parts:
|
|
926
|
+
if other_part.stream_name == join.left_alias:
|
|
927
|
+
alias_to_stream[join.left_alias] = other_part.stream_name
|
|
928
|
+
break
|
|
929
|
+
|
|
930
|
+
# Identify root columns that must be kept
|
|
931
|
+
needed_columns: set[Tuple[str, str]] = set()
|
|
932
|
+
|
|
933
|
+
# 1. All columns in the main part are needed (except those with invalid dependencies)
|
|
934
|
+
for column in view_part.columns:
|
|
935
|
+
col_key = (view_part.stream_name, column.original_name)
|
|
936
|
+
if col_key not in columns_with_invalid_deps:
|
|
937
|
+
needed_columns.add(col_key)
|
|
938
|
+
|
|
939
|
+
# 2. All columns used in join conditions are needed (except those with invalid dependencies)
|
|
940
|
+
for part in all_parts:
|
|
941
|
+
for join in part.joins:
|
|
942
|
+
# Resolve left_alias to actual stream name
|
|
943
|
+
left_stream = alias_to_stream.get(join.left_alias, join.left_alias)
|
|
944
|
+
left_key = (left_stream, join.left_column)
|
|
945
|
+
right_key = (join.join_stream_name, join.join_stream_column)
|
|
946
|
+
if left_key not in columns_with_invalid_deps:
|
|
947
|
+
needed_columns.add(left_key)
|
|
948
|
+
if right_key not in columns_with_invalid_deps:
|
|
949
|
+
needed_columns.add(right_key)
|
|
950
|
+
|
|
951
|
+
logger.debug(f"Identified {len(needed_columns)} root columns to keep (excluding {len(columns_with_invalid_deps)} with invalid deps)")
|
|
952
|
+
|
|
953
|
+
# 3. Find all transitive dependencies using recursive traversal
|
|
954
|
+
# Skip columns with invalid dependencies and their dependents
|
|
955
|
+
def collect_dependencies(col_key: Tuple[str, str], visited: set[Tuple[str, str]]) -> None:
|
|
956
|
+
"""Recursively collect all columns that col_key depends on"""
|
|
957
|
+
if col_key in visited or col_key not in dependency_graph:
|
|
958
|
+
return
|
|
959
|
+
if col_key in columns_with_invalid_deps:
|
|
960
|
+
return # Don't traverse dependencies of invalid columns
|
|
961
|
+
visited.add(col_key)
|
|
962
|
+
|
|
963
|
+
for dep_key in dependency_graph[col_key]:
|
|
964
|
+
if dep_key in all_columns and dep_key not in columns_with_invalid_deps:
|
|
965
|
+
needed_columns.add(dep_key)
|
|
966
|
+
collect_dependencies(dep_key, visited)
|
|
967
|
+
|
|
968
|
+
visited_global: set[Tuple[str, str]] = set()
|
|
969
|
+
for root_col in list(needed_columns):
|
|
970
|
+
collect_dependencies(root_col, visited_global)
|
|
971
|
+
|
|
972
|
+
# Remove columns that are not needed
|
|
973
|
+
columns_removed = False
|
|
974
|
+
for part in all_parts:
|
|
975
|
+
original_count = len(part.columns)
|
|
976
|
+
removed_cols = [col for col in part.columns
|
|
977
|
+
if (part.stream_name, col.original_name) not in needed_columns]
|
|
978
|
+
|
|
979
|
+
# Log warnings for each removed column with the reason
|
|
980
|
+
for col in removed_cols:
|
|
981
|
+
# Determine why the column is being removed
|
|
982
|
+
col_key = (part.stream_name, col.original_name)
|
|
983
|
+
if col.referenced_columns:
|
|
984
|
+
# Check if any referenced columns don't exist
|
|
985
|
+
missing_refs = []
|
|
986
|
+
for ref_stream_name, ref_fields in col.referenced_columns.items():
|
|
987
|
+
resolved_stream = ref_stream_name
|
|
988
|
+
for join in view_part.joins:
|
|
989
|
+
if join.join_stream_alias == ref_stream_name:
|
|
990
|
+
resolved_stream = join.join_stream_name
|
|
991
|
+
break
|
|
992
|
+
for ref_field in ref_fields:
|
|
993
|
+
if (resolved_stream, ref_field) not in all_columns:
|
|
994
|
+
missing_refs.append(f"{ref_field} in {resolved_stream}")
|
|
885
995
|
|
|
886
|
-
|
|
887
|
-
if ref_column is None:
|
|
996
|
+
if missing_refs:
|
|
888
997
|
logger.warning(
|
|
889
|
-
f"
|
|
890
|
-
f"
|
|
998
|
+
f"Removing column {col.original_name} from {part.stream_name} because it references "
|
|
999
|
+
f"non-existent column(s): {', '.join(missing_refs)}"
|
|
891
1000
|
)
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
1001
|
+
else:
|
|
1002
|
+
# Column is not needed (not referenced by main part)
|
|
1003
|
+
logger.debug(
|
|
1004
|
+
f"Removing column {col.original_name} from {part.stream_name} because it is not "
|
|
1005
|
+
f"referenced by the main part or any join conditions"
|
|
1006
|
+
)
|
|
1007
|
+
else:
|
|
1008
|
+
logger.debug(
|
|
1009
|
+
f"Removing column {col.original_name} from {part.stream_name} because it is not "
|
|
1010
|
+
f"referenced by the main part or any join conditions"
|
|
1011
|
+
)
|
|
1012
|
+
|
|
1013
|
+
part.columns = [col for col in part.columns
|
|
1014
|
+
if (part.stream_name, col.original_name) in needed_columns]
|
|
1015
|
+
|
|
1016
|
+
if removed_cols:
|
|
901
1017
|
columns_removed = True
|
|
902
1018
|
|
|
903
|
-
# Process joined parts
|
|
904
|
-
for joined_part in joined_parts:
|
|
905
|
-
# We have to avoid pruning columns that are referenced by joins to this stream.
|
|
906
|
-
# first, we determine all aliases for this stream (multiple join paths back to the same stream are allowed)
|
|
907
|
-
aliases_for_stream = [j.join_stream_alias for j in view_part.joins if j.join_stream_name == joined_part.stream_name]
|
|
908
|
-
# now find all joins using this stream as the join stream
|
|
909
|
-
columns_used_in_joins = [
|
|
910
|
-
j.left_column for j in view_part.joins if j.left_alias in aliases_for_stream
|
|
911
|
-
]
|
|
912
|
-
for column in joined_part.columns[:]: # Use a copy to allow safe removal
|
|
913
|
-
# First check if the column is a join column
|
|
914
|
-
if column.original_name in columns_used_in_joins:
|
|
915
|
-
# If it's a join column, we need to keep it
|
|
916
|
-
continue
|
|
917
|
-
|
|
918
|
-
if not should_keep_column(column, joined_part):
|
|
919
|
-
joined_part.columns.remove(column)
|
|
920
|
-
columns_removed = True
|
|
921
|
-
|
|
922
1019
|
return columns_removed
|
|
923
1020
|
|
|
924
1021
|
class JsonSchemaTopLevel(BaseModel):
|
{omnata_plugin_runtime-0.11.4.dist-info → omnata_plugin_runtime-0.11.5a322.dist-info}/METADATA
RENAMED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: omnata-plugin-runtime
|
|
3
|
-
Version: 0.11.
|
|
3
|
+
Version: 0.11.5a322
|
|
4
4
|
Summary: Classes and common runtime components for building and running Omnata Plugins
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Author: James Weakley
|
|
7
7
|
Author-email: james.weakley@omnata.com
|
|
8
|
-
Requires-Python: >=3.
|
|
8
|
+
Requires-Python: >=3.9,<=3.11
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
11
10
|
Classifier: Programming Language :: Python :: 3.9
|
|
12
11
|
Classifier: Programming Language :: Python :: 3.10
|
|
13
12
|
Classifier: Programming Language :: Python :: 3.11
|
{omnata_plugin_runtime-0.11.4.dist-info → omnata_plugin_runtime-0.11.5a322.dist-info}/RECORD
RENAMED
|
@@ -2,12 +2,12 @@ omnata_plugin_runtime/__init__.py,sha256=MS9d1whnfT_B3-ThqZ7l63QeC_8OEKTuaYV5wTw
|
|
|
2
2
|
omnata_plugin_runtime/api.py,sha256=5gbjbnFy72Xjf0E3kbG23G0V2J3CorvD5kpBn_BkdlI,8084
|
|
3
3
|
omnata_plugin_runtime/configuration.py,sha256=SffokJfgvy6V3kUsoEjXcK3GdNgHo6U3mgBEs0qBv4I,46972
|
|
4
4
|
omnata_plugin_runtime/forms.py,sha256=Lrbr3otsFDrvHWJw7v-slsW4PvEHJ6BG1Yl8oaJfiDo,20529
|
|
5
|
-
omnata_plugin_runtime/json_schema.py,sha256=
|
|
5
|
+
omnata_plugin_runtime/json_schema.py,sha256=ZfHMG-XSJBE9Smt33Y6GPpl5skF7pB1TRCf9AvWuw-Y,59705
|
|
6
6
|
omnata_plugin_runtime/logging.py,sha256=WBuZt8lF9E5oFWM4KYQbE8dDJ_HctJ1pN3BHwU6rcd0,4461
|
|
7
7
|
omnata_plugin_runtime/omnata_plugin.py,sha256=xqAIxFdb2X4ryK4VetQxI4u4UdMyN2xs4toLHKasIdU,142045
|
|
8
8
|
omnata_plugin_runtime/plugin_entrypoints.py,sha256=_1pDLov3iQorGmfcae8Sw2bVjxw1vYeowBaKKNzRclQ,32629
|
|
9
9
|
omnata_plugin_runtime/rate_limiting.py,sha256=qpr5esU4Ks8hMzuMpSR3gLFdor2ZUXYWCjmsQH_K6lQ,25882
|
|
10
|
-
omnata_plugin_runtime-0.11.
|
|
11
|
-
omnata_plugin_runtime-0.11.
|
|
12
|
-
omnata_plugin_runtime-0.11.
|
|
13
|
-
omnata_plugin_runtime-0.11.
|
|
10
|
+
omnata_plugin_runtime-0.11.5a322.dist-info/METADATA,sha256=u9a0Z2qe84l1vagmjgTmZYCU1oULyiPTBRsBrltHC5A,2183
|
|
11
|
+
omnata_plugin_runtime-0.11.5a322.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
12
|
+
omnata_plugin_runtime-0.11.5a322.dist-info/licenses/LICENSE,sha256=rGaMQG3R3F5-JGDp_-rlMKpDIkg5n0SI4kctTk8eZSI,56
|
|
13
|
+
omnata_plugin_runtime-0.11.5a322.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|