pytrilogy 0.0.3.93__tar.gz → 0.0.3.94__tar.gz

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 pytrilogy might be problematic. Click here for more details.

Files changed (153) hide show
  1. {pytrilogy-0.0.3.93/pytrilogy.egg-info → pytrilogy-0.0.3.94}/PKG-INFO +1 -1
  2. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94/pytrilogy.egg-info}/PKG-INFO +1 -1
  3. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/pytrilogy.egg-info/SOURCES.txt +0 -1
  4. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/__init__.py +1 -1
  5. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/env_processor.py +4 -2
  6. pytrilogy-0.0.3.94/trilogy/core/graph_models.py +132 -0
  7. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/models/author.py +16 -25
  8. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/models/build.py +5 -4
  9. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/processing/node_generators/node_merge_node.py +30 -28
  10. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +25 -11
  11. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/processing/node_generators/select_merge_node.py +66 -80
  12. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/parsing/parse_engine.py +1 -1
  13. pytrilogy-0.0.3.93/trilogy/core/graph_models.py +0 -113
  14. pytrilogy-0.0.3.93/trilogy/core/processing/node_generators/select_merge_node_v2.py +0 -792
  15. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/LICENSE.md +0 -0
  16. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/README.md +0 -0
  17. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/pyproject.toml +0 -0
  18. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/pytrilogy.egg-info/dependency_links.txt +0 -0
  19. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/pytrilogy.egg-info/entry_points.txt +0 -0
  20. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/pytrilogy.egg-info/requires.txt +0 -0
  21. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/pytrilogy.egg-info/top_level.txt +0 -0
  22. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/setup.cfg +0 -0
  23. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/setup.py +0 -0
  24. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/tests/test_datatypes.py +0 -0
  25. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/tests/test_declarations.py +0 -0
  26. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/tests/test_derived_concepts.py +0 -0
  27. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/tests/test_discovery_nodes.py +0 -0
  28. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/tests/test_enums.py +0 -0
  29. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/tests/test_environment.py +0 -0
  30. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/tests/test_execute_models.py +0 -0
  31. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/tests/test_executor.py +0 -0
  32. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/tests/test_failure.py +0 -0
  33. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/tests/test_functions.py +0 -0
  34. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/tests/test_imports.py +0 -0
  35. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/tests/test_metadata.py +0 -0
  36. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/tests/test_models.py +0 -0
  37. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/tests/test_multi_join_assignments.py +0 -0
  38. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/tests/test_parse_engine.py +0 -0
  39. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/tests/test_parsing.py +0 -0
  40. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/tests/test_parsing_failures.py +0 -0
  41. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/tests/test_partial_handling.py +0 -0
  42. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/tests/test_query_processing.py +0 -0
  43. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/tests/test_query_render.py +0 -0
  44. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/tests/test_select.py +0 -0
  45. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/tests/test_show.py +0 -0
  46. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/tests/test_statements.py +0 -0
  47. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/tests/test_typing.py +0 -0
  48. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/tests/test_undefined_concept.py +0 -0
  49. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/tests/test_user_functions.py +0 -0
  50. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/tests/test_where_clause.py +0 -0
  51. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/authoring/__init__.py +0 -0
  52. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/compiler.py +0 -0
  53. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/constants.py +0 -0
  54. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/__init__.py +0 -0
  55. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/constants.py +0 -0
  56. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/enums.py +0 -0
  57. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/environment_helpers.py +0 -0
  58. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/ergonomics.py +0 -0
  59. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/exceptions.py +0 -0
  60. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/functions.py +0 -0
  61. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/internal.py +0 -0
  62. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/models/__init__.py +0 -0
  63. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/models/build_environment.py +0 -0
  64. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/models/core.py +0 -0
  65. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/models/datasource.py +0 -0
  66. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/models/environment.py +0 -0
  67. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/models/execute.py +0 -0
  68. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/optimization.py +0 -0
  69. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/optimizations/__init__.py +0 -0
  70. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/optimizations/base_optimization.py +0 -0
  71. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/optimizations/inline_datasource.py +0 -0
  72. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
  73. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/processing/__init__.py +0 -0
  74. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/processing/concept_strategies_v3.py +0 -0
  75. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/processing/discovery_loop.py +0 -0
  76. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/processing/discovery_node_factory.py +0 -0
  77. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/processing/discovery_utility.py +0 -0
  78. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/processing/discovery_validation.py +0 -0
  79. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/processing/graph_utils.py +0 -0
  80. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/processing/node_generators/__init__.py +0 -0
  81. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/processing/node_generators/basic_node.py +0 -0
  82. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/processing/node_generators/common.py +0 -0
  83. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/processing/node_generators/constant_node.py +0 -0
  84. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/processing/node_generators/filter_node.py +0 -0
  85. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/processing/node_generators/group_node.py +0 -0
  86. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
  87. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
  88. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/processing/node_generators/recursive_node.py +0 -0
  89. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
  90. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
  91. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/processing/node_generators/select_node.py +0 -0
  92. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/processing/node_generators/synonym_node.py +0 -0
  93. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/processing/node_generators/union_node.py +0 -0
  94. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
  95. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/processing/node_generators/window_node.py +0 -0
  96. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/processing/nodes/__init__.py +0 -0
  97. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/processing/nodes/base_node.py +0 -0
  98. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/processing/nodes/filter_node.py +0 -0
  99. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/processing/nodes/group_node.py +0 -0
  100. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/processing/nodes/merge_node.py +0 -0
  101. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/processing/nodes/recursive_node.py +0 -0
  102. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
  103. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/processing/nodes/union_node.py +0 -0
  104. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  105. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/processing/nodes/window_node.py +0 -0
  106. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/processing/utility.py +0 -0
  107. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/query_processor.py +0 -0
  108. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/statements/__init__.py +0 -0
  109. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/statements/author.py +0 -0
  110. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/statements/build.py +0 -0
  111. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/statements/common.py +0 -0
  112. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/statements/execute.py +0 -0
  113. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/core/utility.py +0 -0
  114. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/dialect/__init__.py +0 -0
  115. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/dialect/base.py +0 -0
  116. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/dialect/bigquery.py +0 -0
  117. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/dialect/common.py +0 -0
  118. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/dialect/config.py +0 -0
  119. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/dialect/dataframe.py +0 -0
  120. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/dialect/duckdb.py +0 -0
  121. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/dialect/enums.py +0 -0
  122. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/dialect/postgres.py +0 -0
  123. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/dialect/presto.py +0 -0
  124. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/dialect/snowflake.py +0 -0
  125. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/dialect/sql_server.py +0 -0
  126. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/engine.py +0 -0
  127. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/executor.py +0 -0
  128. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/hooks/__init__.py +0 -0
  129. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/hooks/base_hook.py +0 -0
  130. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/hooks/graph_hook.py +0 -0
  131. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/hooks/query_debugger.py +0 -0
  132. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/metadata/__init__.py +0 -0
  133. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/parser.py +0 -0
  134. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/parsing/__init__.py +0 -0
  135. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/parsing/common.py +0 -0
  136. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/parsing/config.py +0 -0
  137. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/parsing/exceptions.py +0 -0
  138. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/parsing/helpers.py +0 -0
  139. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/parsing/render.py +0 -0
  140. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/parsing/trilogy.lark +0 -0
  141. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/py.typed +0 -0
  142. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/render.py +0 -0
  143. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/scripts/__init__.py +0 -0
  144. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/scripts/trilogy.py +0 -0
  145. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/std/__init__.py +0 -0
  146. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/std/date.preql +0 -0
  147. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/std/display.preql +0 -0
  148. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/std/geography.preql +0 -0
  149. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/std/money.preql +0 -0
  150. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/std/net.preql +0 -0
  151. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/std/ranking.preql +0 -0
  152. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/std/report.preql +0 -0
  153. {pytrilogy-0.0.3.93 → pytrilogy-0.0.3.94}/trilogy/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.93
3
+ Version: 0.0.3.94
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.93
3
+ Version: 0.0.3.94
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -90,7 +90,6 @@ trilogy/core/processing/node_generators/node_merge_node.py
90
90
  trilogy/core/processing/node_generators/recursive_node.py
91
91
  trilogy/core/processing/node_generators/rowset_node.py
92
92
  trilogy/core/processing/node_generators/select_merge_node.py
93
- trilogy/core/processing/node_generators/select_merge_node_v2.py
94
93
  trilogy/core/processing/node_generators/select_node.py
95
94
  trilogy/core/processing/node_generators/synonym_node.py
96
95
  trilogy/core/processing/node_generators/union_node.py
@@ -4,6 +4,6 @@ from trilogy.dialect.enums import Dialects
4
4
  from trilogy.executor import Executor
5
5
  from trilogy.parser import parse
6
6
 
7
- __version__ = "0.0.3.93"
7
+ __version__ = "0.0.3.94"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -44,8 +44,10 @@ def add_concept(
44
44
  continue
45
45
  if pseudonym_node.split("@")[0] == node_name.split("@")[0]:
46
46
  continue
47
- g.add_edge(pseudonym_node, node_name, pseudonym=True)
48
- g.add_edge(node_name, pseudonym_node, pseudonym=True)
47
+ g.add_edge(pseudonym_node, node_name)
48
+ g.add_edge(node_name, pseudonym_node)
49
+ g.pseudonyms.add((pseudonym_node, node_name))
50
+ g.pseudonyms.add((node_name, pseudonym_node))
49
51
  add_concept(pseudonym, g, concept_mapping, default_concept_graph, seen)
50
52
 
51
53
 
@@ -0,0 +1,132 @@
1
+ from typing import Union
2
+
3
+ import networkx as nx
4
+
5
+ from trilogy.core.models.build import BuildConcept, BuildDatasource, BuildWhereClause
6
+
7
+
8
+ def get_graph_exact_match(
9
+ g: Union[nx.DiGraph, "ReferenceGraph"],
10
+ accept_partial: bool,
11
+ conditions: BuildWhereClause | None,
12
+ ) -> set[str]:
13
+ exact: set[str] = set()
14
+ for node, ds in g.datasources.items():
15
+ if isinstance(ds, list):
16
+ exact.add(node)
17
+ continue
18
+
19
+ if not conditions and not ds.non_partial_for:
20
+ exact.add(node)
21
+ continue
22
+ elif not conditions and accept_partial and ds.non_partial_for:
23
+ exact.add(node)
24
+ continue
25
+ elif conditions:
26
+ if not ds.non_partial_for:
27
+ continue
28
+ if ds.non_partial_for and conditions == ds.non_partial_for:
29
+ exact.add(node)
30
+ continue
31
+ else:
32
+ continue
33
+
34
+ return exact
35
+
36
+
37
+ def prune_sources_for_conditions(
38
+ g: "ReferenceGraph",
39
+ accept_partial: bool,
40
+ conditions: BuildWhereClause | None,
41
+ ):
42
+ complete = get_graph_exact_match(g, accept_partial, conditions)
43
+ to_remove = []
44
+ for node in g.datasources:
45
+ if node not in complete:
46
+ to_remove.append(node)
47
+
48
+ for node in to_remove:
49
+ g.remove_node(node)
50
+
51
+
52
+ def concept_to_node(input: BuildConcept) -> str:
53
+ # if input.purpose == Purpose.METRIC:
54
+ # return f"c~{input.namespace}.{input.name}@{input.grain}"
55
+ return f"c~{input.address}@{input.grain.str_no_condition}"
56
+
57
+
58
+ def datasource_to_node(input: BuildDatasource) -> str:
59
+ # if isinstance(input, JoinedDataSource):
60
+ # return "ds~join~" + ",".join(
61
+ # [datasource_to_node(sub) for sub in input.datasources]
62
+ # )
63
+ return f"ds~{input.identifier}"
64
+
65
+
66
+ class ReferenceGraph(nx.DiGraph):
67
+ def __init__(self, *args, **kwargs):
68
+ super().__init__(*args, **kwargs)
69
+ self.concepts: dict[str, BuildConcept] = {}
70
+ self.datasources: dict[str, BuildDatasource] = {}
71
+ self.pseudonyms: set[tuple[str, str]] = set()
72
+
73
+ def copy(self):
74
+ g = ReferenceGraph()
75
+ g.concepts = self.concepts.copy()
76
+ g.datasources = self.datasources.copy()
77
+ g.pseudonyms = {*self.pseudonyms}
78
+ # g.add_nodes_from(self.nodes(data=True))
79
+ for node in self.nodes:
80
+ g.add_node(node, fast=True)
81
+ for edge in self.edges:
82
+ g.add_edge(edge[0], edge[1], fast=True)
83
+ # g.add_edges_from(self.edges(data=True))
84
+ return g
85
+
86
+ def remove_node(self, n):
87
+ if n in self.concepts:
88
+ del self.concepts[n]
89
+ if n in self.datasources:
90
+ del self.datasources[n]
91
+ super().remove_node(n)
92
+
93
+ def add_node(self, node_for_adding, fast: bool = False, **attr):
94
+ if fast:
95
+ return super().add_node(node_for_adding, **attr)
96
+ if isinstance(node_for_adding, BuildConcept):
97
+ node_name = concept_to_node(node_for_adding)
98
+ self.concepts[node_name] = node_for_adding
99
+ elif isinstance(node_for_adding, BuildDatasource):
100
+ node_name = datasource_to_node(node_for_adding)
101
+ self.datasources[node_name] = node_for_adding
102
+ else:
103
+ node_name = node_for_adding
104
+ if attr.get("datasource"):
105
+ self.datasources[node_name] = attr["datasource"]
106
+ super().add_node(node_name, **attr)
107
+
108
+ def add_edge(self, u_of_edge, v_of_edge, fast: bool = False, **attr):
109
+ if fast:
110
+ return super().add_edge(u_of_edge, v_of_edge, **attr)
111
+ if isinstance(u_of_edge, BuildConcept):
112
+ orig = u_of_edge
113
+ u_of_edge = concept_to_node(u_of_edge)
114
+ if u_of_edge not in self.nodes:
115
+ self.add_node(orig)
116
+ elif isinstance(u_of_edge, BuildDatasource):
117
+ origd = u_of_edge
118
+ u_of_edge = datasource_to_node(u_of_edge)
119
+ if u_of_edge not in self.nodes:
120
+ self.add_node(origd)
121
+
122
+ if isinstance(v_of_edge, BuildConcept):
123
+ orig = v_of_edge
124
+ v_of_edge = concept_to_node(v_of_edge)
125
+ if v_of_edge not in self.nodes:
126
+ self.add_node(orig)
127
+ elif isinstance(v_of_edge, BuildDatasource):
128
+ origd = v_of_edge
129
+ v_of_edge = datasource_to_node(v_of_edge)
130
+ if v_of_edge not in self.nodes:
131
+ self.add_node(origd)
132
+ super().add_edge(u_of_edge, v_of_edge)
@@ -902,7 +902,11 @@ class Concept(Addressable, DataTyped, ConceptArgs, Mergeable, Namespaced, BaseMo
902
902
 
903
903
  @property
904
904
  def is_aggregate(self):
905
- return self.calculate_is_aggregate(self.lineage)
905
+ base = getattr(self, "_is_aggregate", None)
906
+ if base:
907
+ return base
908
+ setattr(self, "_is_aggregate", self.calculate_is_aggregate(self.lineage))
909
+ return self._is_aggregate
906
910
 
907
911
  def with_merge(self, source: Self, target: Self, modifiers: List[Modifier]) -> Self:
908
912
  if self.address == source.address:
@@ -1069,18 +1073,25 @@ class Concept(Addressable, DataTyped, ConceptArgs, Mergeable, Namespaced, BaseMo
1069
1073
  final_grain = grain if not self.grain.components else self.grain
1070
1074
  keys = self.keys
1071
1075
 
1072
- if self.is_aggregate and isinstance(new_lineage, Function) and grain.components:
1076
+ if self.is_aggregate and grain.components and isinstance(new_lineage, Function):
1073
1077
  grain_components: list[ConceptRef | Concept] = [
1074
1078
  environment.concepts[c].reference for c in grain.components
1075
1079
  ]
1076
- new_lineage = AggregateWrapper(function=new_lineage, by=grain_components)
1080
+ new_lineage = AggregateWrapper.model_construct(
1081
+ function=new_lineage, by=grain_components
1082
+ )
1077
1083
  final_grain = grain
1078
1084
  keys = set(grain.components)
1079
- elif isinstance(new_lineage, AggregateWrapper) and not new_lineage.by and grain:
1085
+ elif (
1086
+ grain
1087
+ and new_lineage
1088
+ and isinstance(new_lineage, AggregateWrapper)
1089
+ and not new_lineage.by
1090
+ ):
1080
1091
  grain_components = [
1081
1092
  environment.concepts[c].reference for c in grain.components
1082
1093
  ]
1083
- new_lineage = AggregateWrapper(
1094
+ new_lineage = AggregateWrapper.model_construct(
1084
1095
  function=new_lineage.function, by=grain_components
1085
1096
  )
1086
1097
  final_grain = grain
@@ -1670,15 +1681,6 @@ class Function(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
1670
1681
  def datatype(self):
1671
1682
  return self.output_datatype
1672
1683
 
1673
- @field_validator("output_datatype")
1674
- @classmethod
1675
- def parse_output_datatype(cls, v, info: ValidationInfo):
1676
- values = info.data
1677
- if values.get("operator") == FunctionType.ATTR_ACCESS:
1678
- if isinstance(v, StructType):
1679
- raise SyntaxError
1680
- return v
1681
-
1682
1684
  @field_validator("arguments", mode="before")
1683
1685
  @classmethod
1684
1686
  def parse_arguments(cls, v, info: ValidationInfo):
@@ -1845,17 +1847,6 @@ class Function(DataTyped, ConceptArgs, Mergeable, Namespaced, BaseModel):
1845
1847
  base += get_concept_arguments(arg)
1846
1848
  return base
1847
1849
 
1848
- @property
1849
- def output_grain(self):
1850
- # aggregates have an abstract grain
1851
- base_grain = Grain(components=[])
1852
- if self.operator in FunctionClass.AGGREGATE_FUNCTIONS.value:
1853
- return base_grain
1854
- # scalars have implicit grain of all arguments
1855
- for input in self.concept_arguments:
1856
- base_grain += input.grain
1857
- return base_grain
1858
-
1859
1850
 
1860
1851
  class FunctionCallWrapper(
1861
1852
  DataTyped,
@@ -1533,6 +1533,7 @@ class Factory:
1533
1533
  )
1534
1534
  self.local_non_build_concepts: dict[str, Concept] = {}
1535
1535
  self.pseudonym_map = pseudonym_map or get_canonical_pseudonyms(environment)
1536
+ self.build_grain = self.build(self.grain) if self.grain else None
1536
1537
 
1537
1538
  def instantiate_concept(
1538
1539
  self,
@@ -1792,11 +1793,11 @@ class Factory:
1792
1793
  address = base.concept.address
1793
1794
  fetched = (
1794
1795
  self._build_concept(
1795
- self.environment.alias_origin_lookup[address].with_grain(self.grain)
1796
- )
1796
+ self.environment.alias_origin_lookup[address]
1797
+ ).with_grain(self.build_grain)
1797
1798
  if address in self.environment.alias_origin_lookup
1798
- else self._build_concept(
1799
- self.environment.concepts[address].with_grain(self.grain)
1799
+ else self._build_concept(self.environment.concepts[address]).with_grain(
1800
+ self.build_grain
1800
1801
  )
1801
1802
  )
1802
1803
 
@@ -6,7 +6,11 @@ from networkx.algorithms import approximation as ax
6
6
  from trilogy.constants import logger
7
7
  from trilogy.core.enums import Derivation
8
8
  from trilogy.core.exceptions import AmbiguousRelationshipResolutionException
9
- from trilogy.core.graph_models import concept_to_node, prune_sources_for_conditions
9
+ from trilogy.core.graph_models import (
10
+ ReferenceGraph,
11
+ concept_to_node,
12
+ prune_sources_for_conditions,
13
+ )
10
14
  from trilogy.core.models.build import BuildConcept, BuildConditional, BuildWhereClause
11
15
  from trilogy.core.models.build_environment import BuildEnvironment
12
16
  from trilogy.core.processing.nodes import History, MergeNode, StrategyNode
@@ -17,11 +21,12 @@ LOGGER_PREFIX = "[GEN_MERGE_NODE]"
17
21
  AMBIGUITY_CHECK_LIMIT = 20
18
22
 
19
23
 
20
- def filter_pseudonyms_for_source(ds_graph: nx.DiGraph, node: str):
24
+ def filter_pseudonyms_for_source(
25
+ ds_graph: nx.DiGraph, node: str, pseudonyms: set[tuple[str, str]]
26
+ ):
21
27
  to_remove = set()
22
-
23
28
  for edge in ds_graph.edges:
24
- if ds_graph.edges[edge].get("pseudonym", False):
29
+ if edge in pseudonyms:
25
30
  lengths = {}
26
31
  for n in edge:
27
32
  lengths[n] = nx.shortest_path_length(ds_graph, node, n)
@@ -52,12 +57,14 @@ def filter_unique_graphs(graphs: list[list[str]]) -> list[list[str]]:
52
57
  return [list(x) for x in unique_graphs]
53
58
 
54
59
 
55
- def extract_ds_components(g: nx.DiGraph, nodelist: list[str]) -> list[list[str]]:
60
+ def extract_ds_components(
61
+ g: nx.DiGraph, nodelist: list[str], pseudonyms: set[tuple[str, str]]
62
+ ) -> list[list[str]]:
56
63
  graphs = []
57
64
  for node in g.nodes:
58
65
  if node.startswith("ds~"):
59
66
  local = g.copy()
60
- filter_pseudonyms_for_source(local, node)
67
+ filter_pseudonyms_for_source(local, node, pseudonyms)
61
68
  ds_graph: nx.DiGraph = nx.ego_graph(local, node, radius=10).copy()
62
69
  graphs.append(
63
70
  [
@@ -78,7 +85,7 @@ def extract_ds_components(g: nx.DiGraph, nodelist: list[str]) -> list[list[str]]
78
85
 
79
86
 
80
87
  def determine_induced_minimal_nodes(
81
- G: nx.DiGraph,
88
+ G: ReferenceGraph,
82
89
  nodelist: list[str],
83
90
  environment: BuildEnvironment,
84
91
  filter_downstream: bool,
@@ -86,23 +93,19 @@ def determine_induced_minimal_nodes(
86
93
  ) -> nx.DiGraph | None:
87
94
  H: nx.Graph = nx.to_undirected(G).copy()
88
95
  nodes_to_remove = []
89
- concepts = nx.get_node_attributes(G, "concept")
90
-
91
- for node in G.nodes:
92
- if concepts.get(node):
93
- lookup: BuildConcept = concepts[node]
94
- # inclusion of aggregates can create ambiguous node relation chains
95
- # there may be a better way to handle this
96
- # can be revisited if we need to connect a derived synonym based on an aggregate
97
- if lookup.derivation in (
98
- Derivation.CONSTANT,
99
- Derivation.AGGREGATE,
100
- Derivation.FILTER,
101
- ):
102
- nodes_to_remove.append(node)
103
- # purge a node if we're already looking for all it's parents
104
- if filter_downstream and lookup.derivation not in (Derivation.ROOT,):
105
- nodes_to_remove.append(node)
96
+ for node, lookup in G.concepts.items():
97
+ # inclusion of aggregates can create ambiguous node relation chains
98
+ # there may be a better way to handle this
99
+ # can be revisited if we need to connect a derived synonym based on an aggregate
100
+ if lookup.derivation in (
101
+ Derivation.CONSTANT,
102
+ Derivation.AGGREGATE,
103
+ Derivation.FILTER,
104
+ ):
105
+ nodes_to_remove.append(node)
106
+ # purge a node if we're already looking for all it's parents
107
+ if filter_downstream and lookup.derivation not in (Derivation.ROOT,):
108
+ nodes_to_remove.append(node)
106
109
  if nodes_to_remove:
107
110
  # logger.debug(f"Removing nodes {nodes_to_remove} from graph")
108
111
  H.remove_nodes_from(nodes_to_remove)
@@ -259,7 +262,7 @@ def filter_duplicate_subgraphs(
259
262
  def resolve_weak_components(
260
263
  all_concepts: List[BuildConcept],
261
264
  environment: BuildEnvironment,
262
- environment_graph: nx.DiGraph,
265
+ environment_graph: ReferenceGraph,
263
266
  filter_downstream: bool = True,
264
267
  accept_partial: bool = False,
265
268
  search_conditions: BuildWhereClause | None = None,
@@ -316,8 +319,6 @@ def resolve_weak_components(
316
319
  ]
317
320
  new = [x for x in all_graph_concepts if x.address not in all_concepts]
318
321
 
319
- new_addresses = set([x.address for x in new if x.address not in synonyms])
320
-
321
322
  if not new:
322
323
  break_flag = True
323
324
  # remove our new nodes for the next search path
@@ -329,6 +330,7 @@ def resolve_weak_components(
329
330
  # from trilogy.hooks.graph_hook import GraphHook
330
331
  # GraphHook().query_graph_built(g, highlight_nodes=[concept_to_node(c.with_default_grain()) for c in all_concepts if "__preql_internal" not in c.address])
331
332
  found.append(g)
333
+ new_addresses = set([x.address for x in new if x.address not in synonyms])
332
334
  reduced_concept_sets.append(new_addresses)
333
335
 
334
336
  except nx.exception.NetworkXNoPath:
@@ -346,7 +348,7 @@ def resolve_weak_components(
346
348
  subgraphs: list[list[BuildConcept]] = []
347
349
  # components = nx.strongly_connected_components(g)
348
350
  node_list = [x for x in g.nodes if x.startswith("c~")]
349
- components = extract_ds_components(g, node_list)
351
+ components = extract_ds_components(g, node_list, environment_graph.pseudonyms)
350
352
  logger.debug(f"Extracted components {components} from {node_list}")
351
353
  for component in components:
352
354
  # we need to take unique again as different addresses may map to the same concept
@@ -128,17 +128,32 @@ def simplify_conditions(
128
128
  for condition in conditions:
129
129
  if not isinstance(condition, BuildComparison):
130
130
  return False
131
- if not isinstance(condition.left, REDUCABLE_TYPES) and not isinstance(
132
- condition.right, REDUCABLE_TYPES
133
- ):
134
- return False
135
- if not isinstance(condition.left, BuildConcept) and not isinstance(
136
- condition.right, BuildConcept
131
+ left_is_concept = False
132
+ left_is_reducable = False
133
+ right_is_concept = False
134
+ right_is_reducable = False
135
+ if isinstance(condition.left, BuildConcept):
136
+ left_is_concept = True
137
+ elif isinstance(condition.left, REDUCABLE_TYPES):
138
+ left_is_reducable = True
139
+
140
+ if isinstance(condition.right, BuildConcept):
141
+ right_is_concept = True
142
+ elif isinstance(condition.right, REDUCABLE_TYPES):
143
+ right_is_reducable = True
144
+
145
+ if not (
146
+ (left_is_concept and right_is_reducable)
147
+ or (right_is_concept and left_is_reducable)
137
148
  ):
138
149
  return False
139
- vars = [condition.left, condition.right]
140
- concept = [x for x in vars if isinstance(x, BuildConcept)][0]
141
- raw_comparison = [x for x in vars if not isinstance(x, BuildConcept)][0]
150
+ if left_is_concept:
151
+ concept = condition.left
152
+ raw_comparison = condition.right
153
+ else:
154
+ concept = condition.right
155
+ raw_comparison = condition.left
156
+
142
157
  if isinstance(raw_comparison, BuildFunction):
143
158
  if not raw_comparison.operator == FunctionType.CONSTANT:
144
159
  return False
@@ -154,7 +169,7 @@ def simplify_conditions(
154
169
  if not isinstance(comparison, REDUCABLE_TYPES):
155
170
  return False
156
171
 
157
- var = concept
172
+ var: BuildConcept = concept # type: ignore
158
173
  op = condition.operator
159
174
  grouped[var].append((op, comparison))
160
175
 
@@ -240,7 +255,6 @@ def get_union_sources(
240
255
  assocs[merge_key.address].append(x)
241
256
  final: list[list[BuildDatasource]] = []
242
257
  for _, dses in assocs.items():
243
-
244
258
  conditions = [c.non_partial_for.conditional for c in dses if c.non_partial_for]
245
259
  if simplify_conditions(conditions):
246
260
  final.append(dses)