pytrilogy 0.0.2.23__tar.gz → 0.0.2.25__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 (107) hide show
  1. {pytrilogy-0.0.2.23/pytrilogy.egg-info → pytrilogy-0.0.2.25}/PKG-INFO +1 -1
  2. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25/pytrilogy.egg-info}/PKG-INFO +1 -1
  3. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/__init__.py +1 -1
  4. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/env_processor.py +12 -6
  5. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/environment_helpers.py +0 -1
  6. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/models.py +57 -19
  7. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/processing/concept_strategies_v3.py +23 -4
  8. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/processing/node_generators/node_merge_node.py +4 -4
  9. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/processing/utility.py +9 -6
  10. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/dialect/base.py +5 -1
  11. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/LICENSE.md +0 -0
  12. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/README.md +0 -0
  13. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/pyproject.toml +0 -0
  14. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/pytrilogy.egg-info/SOURCES.txt +0 -0
  15. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/pytrilogy.egg-info/dependency_links.txt +0 -0
  16. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/pytrilogy.egg-info/entry_points.txt +0 -0
  17. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/pytrilogy.egg-info/requires.txt +0 -0
  18. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/pytrilogy.egg-info/top_level.txt +0 -0
  19. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/setup.cfg +0 -0
  20. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/setup.py +0 -0
  21. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/tests/test_datatypes.py +0 -0
  22. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/tests/test_declarations.py +0 -0
  23. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/tests/test_derived_concepts.py +0 -0
  24. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/tests/test_discovery_nodes.py +0 -0
  25. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/tests/test_environment.py +0 -0
  26. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/tests/test_functions.py +0 -0
  27. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/tests/test_imports.py +0 -0
  28. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/tests/test_metadata.py +0 -0
  29. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/tests/test_models.py +0 -0
  30. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/tests/test_multi_join_assignments.py +0 -0
  31. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/tests/test_parsing.py +0 -0
  32. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/tests/test_partial_handling.py +0 -0
  33. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/tests/test_query_processing.py +0 -0
  34. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/tests/test_select.py +0 -0
  35. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/tests/test_show.py +0 -0
  36. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/tests/test_statements.py +0 -0
  37. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/tests/test_undefined_concept.py +0 -0
  38. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/tests/test_where_clause.py +0 -0
  39. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/compiler.py +0 -0
  40. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/constants.py +0 -0
  41. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/__init__.py +0 -0
  42. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/constants.py +0 -0
  43. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/enums.py +0 -0
  44. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/ergonomics.py +0 -0
  45. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/exceptions.py +0 -0
  46. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/functions.py +0 -0
  47. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/graph_models.py +0 -0
  48. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/internal.py +0 -0
  49. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/optimization.py +0 -0
  50. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/optimizations/__init__.py +0 -0
  51. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/optimizations/base_optimization.py +0 -0
  52. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/optimizations/inline_constant.py +0 -0
  53. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/optimizations/inline_datasource.py +0 -0
  54. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
  55. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/processing/__init__.py +0 -0
  56. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/processing/graph_utils.py +0 -0
  57. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/processing/node_generators/__init__.py +0 -0
  58. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/processing/node_generators/basic_node.py +0 -0
  59. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/processing/node_generators/common.py +0 -0
  60. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/processing/node_generators/filter_node.py +0 -0
  61. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/processing/node_generators/group_node.py +0 -0
  62. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
  63. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
  64. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
  65. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
  66. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/processing/node_generators/select_node.py +0 -0
  67. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
  68. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/processing/node_generators/window_node.py +0 -0
  69. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/processing/nodes/__init__.py +0 -0
  70. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/processing/nodes/base_node.py +0 -0
  71. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/processing/nodes/filter_node.py +0 -0
  72. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/processing/nodes/group_node.py +0 -0
  73. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/processing/nodes/merge_node.py +0 -0
  74. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
  75. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  76. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/processing/nodes/window_node.py +0 -0
  77. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/core/query_processor.py +0 -0
  78. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/dialect/__init__.py +0 -0
  79. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/dialect/bigquery.py +0 -0
  80. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/dialect/common.py +0 -0
  81. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/dialect/config.py +0 -0
  82. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/dialect/duckdb.py +0 -0
  83. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/dialect/enums.py +0 -0
  84. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/dialect/postgres.py +0 -0
  85. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/dialect/presto.py +0 -0
  86. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/dialect/snowflake.py +0 -0
  87. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/dialect/sql_server.py +0 -0
  88. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/engine.py +0 -0
  89. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/executor.py +0 -0
  90. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/hooks/__init__.py +0 -0
  91. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/hooks/base_hook.py +0 -0
  92. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/hooks/graph_hook.py +0 -0
  93. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/hooks/query_debugger.py +0 -0
  94. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/metadata/__init__.py +0 -0
  95. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/parser.py +0 -0
  96. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/parsing/__init__.py +0 -0
  97. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/parsing/common.py +0 -0
  98. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/parsing/config.py +0 -0
  99. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/parsing/exceptions.py +0 -0
  100. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/parsing/helpers.py +0 -0
  101. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/parsing/parse_engine.py +0 -0
  102. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/parsing/render.py +0 -0
  103. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/parsing/trilogy.lark +0 -0
  104. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/py.typed +0 -0
  105. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/scripts/__init__.py +0 -0
  106. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/scripts/trilogy.py +0 -0
  107. {pytrilogy-0.0.2.23 → pytrilogy-0.0.2.25}/trilogy/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pytrilogy
3
- Version: 0.0.2.23
3
+ Version: 0.0.2.25
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.1
2
2
  Name: pytrilogy
3
- Version: 0.0.2.23
3
+ Version: 0.0.2.25
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -4,6 +4,6 @@ from trilogy.executor import Executor
4
4
  from trilogy.parser import parse
5
5
  from trilogy.constants import CONFIG
6
6
 
7
- __version__ = "0.0.2.23"
7
+ __version__ = "0.0.2.25"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -6,17 +6,20 @@ from trilogy.core.graph_models import (
6
6
  from trilogy.core.models import Environment, Concept, Datasource
7
7
 
8
8
 
9
- def add_concept(concept: Concept, g: ReferenceGraph):
9
+ def add_concept(
10
+ concept: Concept, g: ReferenceGraph, concept_mapping: dict[str, Concept]
11
+ ):
10
12
  g.add_node(concept)
11
13
  # if we have sources, recursively add them
12
14
  node_name = concept_to_node(concept)
13
15
  if concept.concept_arguments:
14
16
  for source in concept.concept_arguments:
15
17
  generic = source.with_default_grain()
16
- add_concept(generic, g)
18
+ add_concept(generic, g, concept_mapping)
17
19
 
18
20
  g.add_edge(generic, node_name)
19
- for _, pseudonym in concept.pseudonyms.items():
21
+ for ps_address in concept.pseudonyms:
22
+ pseudonym = concept_mapping[ps_address]
20
23
  pseudonym = pseudonym.with_default_grain()
21
24
  pseudonym_node = concept_to_node(pseudonym)
22
25
  if (pseudonym_node, node_name) in g.edges and (
@@ -28,7 +31,7 @@ def add_concept(concept: Concept, g: ReferenceGraph):
28
31
  continue
29
32
  g.add_edge(pseudonym_node, node_name, pseudonym=True)
30
33
  g.add_edge(node_name, pseudonym_node, pseudonym=True)
31
- add_concept(pseudonym, g)
34
+ add_concept(pseudonym, g, concept_mapping)
32
35
 
33
36
 
34
37
  def generate_adhoc_graph(
@@ -37,10 +40,11 @@ def generate_adhoc_graph(
37
40
  restrict_to_listed: bool = False,
38
41
  ) -> ReferenceGraph:
39
42
  g = ReferenceGraph()
43
+ concept_mapping = {x.address: x for x in concepts}
40
44
 
41
45
  # add all parsed concepts
42
46
  for concept in concepts:
43
- add_concept(concept, g)
47
+ add_concept(concept, g, concept_mapping)
44
48
 
45
49
  for dataset in datasources:
46
50
  node = datasource_to_node(dataset)
@@ -66,5 +70,7 @@ def generate_graph(
66
70
  ) -> ReferenceGraph:
67
71
 
68
72
  return generate_adhoc_graph(
69
- list(environment.concepts.values()), list(environment.datasources.values())
73
+ list(environment.concepts.values())
74
+ + list(environment.alias_origin_lookup.values()),
75
+ list(environment.datasources.values()),
70
76
  )
@@ -191,4 +191,3 @@ def generate_related_concepts(
191
191
  environment.add_concept(auto, meta=meta)
192
192
  if isinstance(value, Concept):
193
193
  environment.merge_concept(auto, value, modifiers=[])
194
- assert value.pseudonyms is not None
@@ -102,6 +102,12 @@ def get_version():
102
102
  return __version__
103
103
 
104
104
 
105
+ def address_with_namespace(address: str, namespace: str) -> str:
106
+ if address.split(".", 1)[0] == DEFAULT_NAMESPACE:
107
+ return f"{namespace}.{address.split('.',1)[1]}"
108
+ return f"{namespace}.{address}"
109
+
110
+
105
111
  def get_concept_arguments(expr) -> List["Concept"]:
106
112
  output = []
107
113
  if isinstance(expr, Concept):
@@ -436,7 +442,7 @@ class Concept(Mergeable, Namespaced, SelectContext, BaseModel):
436
442
  keys: Optional[Tuple["Concept", ...]] = None
437
443
  grain: "Grain" = Field(default=None, validate_default=True)
438
444
  modifiers: Optional[List[Modifier]] = Field(default_factory=list)
439
- pseudonyms: Dict[str, Concept] = Field(default_factory=dict)
445
+ pseudonyms: set[str] = Field(default_factory=set)
440
446
  _address_cache: str | None = None
441
447
 
442
448
  def __hash__(self):
@@ -462,7 +468,7 @@ class Concept(Mergeable, Namespaced, SelectContext, BaseModel):
462
468
  def with_merge(self, source: Concept, target: Concept, modifiers: List[Modifier]):
463
469
  if self.address == source.address:
464
470
  new = target.with_grain(self.grain.with_merge(source, target, modifiers))
465
- new.pseudonyms[self.address] = self
471
+ new.pseudonyms.add(self.address)
466
472
  return new
467
473
  return self.__class__(
468
474
  name=self.name,
@@ -616,9 +622,7 @@ class Concept(Mergeable, Namespaced, SelectContext, BaseModel):
616
622
  else None
617
623
  ),
618
624
  modifiers=self.modifiers,
619
- pseudonyms={
620
- k: v.with_namespace(namespace) for k, v in self.pseudonyms.items()
621
- },
625
+ pseudonyms={address_with_namespace(v, namespace) for v in self.pseudonyms},
622
626
  )
623
627
 
624
628
  def with_select_context(
@@ -862,7 +866,7 @@ class Grain(Mergeable, BaseModel):
862
866
  )
863
867
  else:
864
868
  v2 = unique(v, "address")
865
- final = []
869
+ final: List[Concept] = []
866
870
  for sub in v2:
867
871
  if sub.purpose in (Purpose.PROPERTY, Purpose.METRIC) and sub.keys:
868
872
  if all([c in v2 for c in sub.keys]):
@@ -916,6 +920,20 @@ class Grain(Mergeable, BaseModel):
916
920
  [c.name == ALL_ROWS_CONCEPT for c in self.components]
917
921
  )
918
922
 
923
+ @property
924
+ def synonym_set(self) -> set[str]:
925
+ base = []
926
+ for x in self.components_copy:
927
+ if isinstance(x.lineage, RowsetItem):
928
+ base.append(x.lineage.content.address)
929
+ for c in x.lineage.content.pseudonyms:
930
+ base.append(c)
931
+ else:
932
+ base.append(x.address)
933
+ for c in x.pseudonyms:
934
+ base.append(c)
935
+ return set(base)
936
+
919
937
  @cached_property
920
938
  def set(self) -> set[str]:
921
939
  base = []
@@ -931,7 +949,11 @@ class Grain(Mergeable, BaseModel):
931
949
  return self.set == set([c.address for c in other])
932
950
  if not isinstance(other, Grain):
933
951
  return False
934
- return self.set == other.set
952
+ if self.set == other.set:
953
+ return True
954
+ elif self.synonym_set == other.synonym_set:
955
+ return True
956
+ return False
935
957
 
936
958
  def issubset(self, other: "Grain"):
937
959
  return self.set.issubset(other.set)
@@ -1584,13 +1606,6 @@ class RawSQLStatement(BaseModel):
1584
1606
  meta: Optional[Metadata] = Field(default_factory=lambda: Metadata())
1585
1607
 
1586
1608
 
1587
- class CopyStatement(BaseModel):
1588
- target: str
1589
- target_type: IOType
1590
- meta: Optional[Metadata] = Field(default_factory=lambda: Metadata())
1591
- select: SelectStatement
1592
-
1593
-
1594
1609
  class SelectStatement(Mergeable, Namespaced, SelectTypeMixin, BaseModel):
1595
1610
  selection: List[SelectItem]
1596
1611
  order_by: Optional[OrderBy] = None
@@ -1789,6 +1804,16 @@ class SelectStatement(Mergeable, Namespaced, SelectTypeMixin, BaseModel):
1789
1804
  )
1790
1805
 
1791
1806
 
1807
+ class CopyStatement(BaseModel):
1808
+ target: str
1809
+ target_type: IOType
1810
+ meta: Optional[Metadata] = Field(default_factory=lambda: Metadata())
1811
+ select: SelectStatement
1812
+
1813
+ def refresh_bindings(self, environment: Environment):
1814
+ self.select.refresh_bindings(environment)
1815
+
1816
+
1792
1817
  class AlignItem(Namespaced, BaseModel):
1793
1818
  alias: str
1794
1819
  concepts: List[Concept]
@@ -2259,7 +2284,7 @@ class BaseJoin(BaseModel):
2259
2284
  for ds in [self.left_datasource, self.right_datasource]:
2260
2285
  synonyms = []
2261
2286
  for c in ds.output_concepts:
2262
- synonyms += list(c.pseudonyms.keys())
2287
+ synonyms += list(c.pseudonyms)
2263
2288
  if (
2264
2289
  concept.address not in [c.address for c in ds.output_concepts]
2265
2290
  and concept.address not in synonyms
@@ -2834,9 +2859,21 @@ class CTE(BaseModel):
2834
2859
  return self.parent_ctes[0].name
2835
2860
  return self.name
2836
2861
 
2862
+ def get_concept(self, address: str) -> Concept | None:
2863
+ for cte in self.parent_ctes:
2864
+ if address in cte.output_columns:
2865
+ match = [x for x in cte.output_columns if x.address == address].pop()
2866
+ return match
2867
+
2868
+ for array in [self.source.input_concepts, self.source.output_concepts]:
2869
+ match_list = [x for x in array if x.address == address]
2870
+ if match_list:
2871
+ return match_list.pop()
2872
+ return None
2873
+
2837
2874
  def get_alias(self, concept: Concept, source: str | None = None) -> str:
2838
2875
  for cte in self.parent_ctes:
2839
- if concept.address in [x.address for x in cte.output_columns]:
2876
+ if concept.address in cte.output_columns:
2840
2877
  if source and source != cte.name:
2841
2878
  continue
2842
2879
  return concept.safe_address
@@ -2988,7 +3025,7 @@ class UndefinedConcept(Concept, Mergeable, Namespaced):
2988
3025
  ) -> "UndefinedConcept" | Concept:
2989
3026
  if self.address == source.address:
2990
3027
  new = target.with_grain(self.grain.with_merge(source, target, modifiers))
2991
- new.pseudonyms[self.address] = self
3028
+ new.pseudonyms.add(self.address)
2992
3029
  return new
2993
3030
  return self.__class__(
2994
3031
  name=self.name,
@@ -3520,6 +3557,7 @@ class Environment(BaseModel):
3520
3557
  self, source: Concept, target: Concept, modifiers: List[Modifier]
3521
3558
  ):
3522
3559
  replacements = {}
3560
+
3523
3561
  # exit early if we've run this
3524
3562
  if source.address in self.alias_origin_lookup:
3525
3563
  if self.concepts[source.address] == target:
@@ -3528,11 +3566,11 @@ class Environment(BaseModel):
3528
3566
  for k, v in self.concepts.items():
3529
3567
 
3530
3568
  if v.address == target.address:
3531
- v.pseudonyms[source.address] = source
3569
+ v.pseudonyms.add(source.address)
3532
3570
  if v.address == source.address:
3533
3571
  replacements[k] = target
3534
3572
  self.canonical_map[k] = target.address
3535
- v.pseudonyms[target.address] = target
3573
+ v.pseudonyms.add(target.address)
3536
3574
  # we need to update keys and grains of all concepts
3537
3575
  else:
3538
3576
  replacements[k] = v.with_merge(source, target, modifiers)
@@ -455,7 +455,20 @@ def generate_node(
455
455
  if x.address not in [y.address for y in root_targets]
456
456
  and x not in ex_resolve.grain.components
457
457
  ]
458
- expanded.set_output_concepts(root_targets)
458
+
459
+ pseudonyms = [
460
+ x
461
+ for x in extra
462
+ if any(x.address in y.pseudonyms for y in root_targets)
463
+ ]
464
+ # if we're only connected by a pseudonym, keep those in output
465
+ expanded.set_output_concepts(root_targets + pseudonyms)
466
+ # but hide them
467
+ if pseudonyms:
468
+ logger.info(
469
+ f"{depth_to_prefix(depth)}{LOGGER_PREFIX} Hiding pseudonyms{[c.address for c in pseudonyms]}"
470
+ )
471
+ expanded.hide_output_concepts(pseudonyms)
459
472
 
460
473
  logger.info(
461
474
  f"{depth_to_prefix(depth)}{LOGGER_PREFIX} Found connections for {[c.address for c in root_targets]} via concept addition; removing extra {[c.address for c in extra]}"
@@ -480,6 +493,7 @@ def validate_concept(
480
493
  found_map: dict[str, set[Concept]],
481
494
  accept_partial: bool,
482
495
  seen: set[str],
496
+ environment: Environment,
483
497
  ):
484
498
 
485
499
  found_map[str(node)].add(concept)
@@ -500,10 +514,11 @@ def validate_concept(
500
514
  if accept_partial:
501
515
  found_addresses.add(concept.address)
502
516
  found_map[str(node)].add(concept)
503
- for _, v in concept.pseudonyms.items():
504
- if v.address == concept.address:
517
+ for v_address in concept.pseudonyms:
518
+ v = environment.concepts[v_address]
519
+ if v == concept.address:
505
520
  return
506
- if v.address in seen:
521
+ if v in seen:
507
522
  return
508
523
  validate_concept(
509
524
  v,
@@ -515,10 +530,12 @@ def validate_concept(
515
530
  found_map,
516
531
  accept_partial,
517
532
  seen=seen,
533
+ environment=environment,
518
534
  )
519
535
 
520
536
 
521
537
  def validate_stack(
538
+ environment: Environment,
522
539
  stack: List[StrategyNode],
523
540
  concepts: List[Concept],
524
541
  mandatory_with_filter: List[Concept],
@@ -546,6 +563,7 @@ def validate_stack(
546
563
  found_map,
547
564
  accept_partial,
548
565
  seen,
566
+ environment,
549
567
  )
550
568
  for concept in node.virtual_output_concepts:
551
569
  if concept.address in non_partial_addresses:
@@ -807,6 +825,7 @@ def _search_concepts(
807
825
  break
808
826
  attempted.add(priority_concept.address)
809
827
  complete, found, missing, partial, virtual = validate_stack(
828
+ environment,
810
829
  stack,
811
830
  mandatory_list,
812
831
  completion_mandatory,
@@ -209,9 +209,9 @@ def resolve_weak_components(
209
209
  for c in all_concepts
210
210
  if "__preql_internal" not in c.address
211
211
  ]
212
- synonyms: list[Concept] = []
212
+ synonyms: set[str] = set()
213
213
  for x in all_concepts:
214
- synonyms += x.pseudonyms.values()
214
+ synonyms = synonyms.union(x.pseudonyms)
215
215
  while break_flag is not True:
216
216
  count += 1
217
217
  if count > AMBIGUITY_CHECK_LIMIT:
@@ -385,9 +385,9 @@ def gen_merge_node(
385
385
  # one concept handling may need to be kicked to alias
386
386
  if len(all_concepts) == 1:
387
387
  concept = all_concepts[0]
388
- for k, v in concept.pseudonyms.items():
388
+ for v in concept.pseudonyms:
389
389
  test = subgraphs_to_merge_node(
390
- [[concept, v]],
390
+ [[concept, environment.alias_origin_lookup[v]]],
391
391
  g=g,
392
392
  all_concepts=[concept],
393
393
  environment=environment,
@@ -162,17 +162,21 @@ def add_node_join_concept(
162
162
  concept: Concept,
163
163
  datasource: Datasource | QueryDatasource,
164
164
  concepts: List[Concept],
165
+ environment: Environment,
165
166
  ):
166
167
 
167
168
  concepts.append(concept)
168
169
 
169
170
  graph.add_node(concept.address, type=NodeType.CONCEPT)
170
171
  graph.add_edge(datasource.identifier, concept.address)
171
- for _, v in concept.pseudonyms.items():
172
+ for v_address in concept.pseudonyms:
173
+ v = environment.alias_origin_lookup.get(
174
+ v_address, environment.concepts[v_address]
175
+ )
172
176
  if v in concepts:
173
177
  continue
174
- if v.address != concept.address:
175
- add_node_join_concept(graph, v, datasource, concepts)
178
+ if v != concept.address:
179
+ add_node_join_concept(graph, v, datasource, concepts, environment)
176
180
 
177
181
 
178
182
  def get_node_joins(
@@ -186,7 +190,7 @@ def get_node_joins(
186
190
  for datasource in datasources:
187
191
  graph.add_node(datasource.identifier, type=NodeType.NODE)
188
192
  for concept in datasource.output_concepts:
189
- add_node_join_concept(graph, concept, datasource, concepts)
193
+ add_node_join_concept(graph, concept, datasource, concepts, environment)
190
194
 
191
195
  # add edges for every constant to every datasource
192
196
  for datasource in datasources:
@@ -195,7 +199,6 @@ def get_node_joins(
195
199
  for node in graph.nodes:
196
200
  if graph.nodes[node]["type"] == NodeType.NODE:
197
201
  graph.add_edge(node, concept.address)
198
-
199
202
  joins: defaultdict[str, set] = defaultdict(set)
200
203
  identifier_map: dict[str, Datasource | QueryDatasource] = {
201
204
  x.identifier: x for x in datasources
@@ -206,7 +209,7 @@ def get_node_joins(
206
209
  # if we're looking up a pseudonym, we would have gotten the remapped value
207
210
  # so double check we got what we were looking for
208
211
  if env_lookup.address == g.address:
209
- grain_pseudonyms.update(env_lookup.pseudonyms.keys())
212
+ grain_pseudonyms.update(env_lookup.pseudonyms)
210
213
 
211
214
  node_list = sorted(
212
215
  [x for x in graph.nodes if graph.nodes[x]["type"] == NodeType.NODE],
@@ -263,7 +263,11 @@ class BaseDialect:
263
263
  ) -> str:
264
264
  result = None
265
265
  if c.pseudonyms:
266
- for candidate in [c] + list(c.pseudonyms.values()):
266
+ candidates = [y for y in [cte.get_concept(x) for x in c.pseudonyms] if y]
267
+ logger.debug(
268
+ f"{LOGGER_PREFIX} [{c.address}] pseudonym candidates are {[x.address for x in candidates]}"
269
+ )
270
+ for candidate in [c] + candidates:
267
271
  try:
268
272
  logger.debug(
269
273
  f"{LOGGER_PREFIX} [{c.address}] Attempting rendering w/ candidate {candidate.address}"
File without changes
File without changes
File without changes
File without changes