pytrilogy 0.0.3.51__tar.gz → 0.0.3.52__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 (140) hide show
  1. {pytrilogy-0.0.3.51/pytrilogy.egg-info → pytrilogy-0.0.3.52}/PKG-INFO +1 -1
  2. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52/pytrilogy.egg-info}/PKG-INFO +1 -1
  3. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/tests/test_parsing.py +12 -4
  4. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/__init__.py +1 -1
  5. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/functions.py +5 -2
  6. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/models/author.py +6 -7
  7. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/models/build.py +3 -9
  8. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/models/execute.py +25 -11
  9. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/parsing/common.py +3 -3
  10. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/parsing/parse_engine.py +10 -5
  11. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/LICENSE.md +0 -0
  12. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/README.md +0 -0
  13. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/pyproject.toml +0 -0
  14. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/pytrilogy.egg-info/SOURCES.txt +0 -0
  15. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/pytrilogy.egg-info/dependency_links.txt +0 -0
  16. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/pytrilogy.egg-info/entry_points.txt +0 -0
  17. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/pytrilogy.egg-info/requires.txt +0 -0
  18. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/pytrilogy.egg-info/top_level.txt +0 -0
  19. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/setup.cfg +0 -0
  20. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/setup.py +0 -0
  21. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/tests/test_datatypes.py +0 -0
  22. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/tests/test_declarations.py +0 -0
  23. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/tests/test_derived_concepts.py +0 -0
  24. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/tests/test_discovery_nodes.py +0 -0
  25. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/tests/test_enums.py +0 -0
  26. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/tests/test_environment.py +0 -0
  27. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/tests/test_executor.py +0 -0
  28. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/tests/test_failure.py +0 -0
  29. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/tests/test_functions.py +0 -0
  30. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/tests/test_imports.py +0 -0
  31. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/tests/test_metadata.py +0 -0
  32. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/tests/test_models.py +0 -0
  33. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/tests/test_multi_join_assignments.py +0 -0
  34. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/tests/test_parse_engine.py +0 -0
  35. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/tests/test_parsing_failures.py +0 -0
  36. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/tests/test_partial_handling.py +0 -0
  37. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/tests/test_query_processing.py +0 -0
  38. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/tests/test_query_render.py +0 -0
  39. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/tests/test_select.py +0 -0
  40. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/tests/test_show.py +0 -0
  41. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/tests/test_statements.py +0 -0
  42. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/tests/test_typing.py +0 -0
  43. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/tests/test_undefined_concept.py +0 -0
  44. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/tests/test_user_functions.py +0 -0
  45. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/tests/test_where_clause.py +0 -0
  46. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/authoring/__init__.py +0 -0
  47. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/compiler.py +0 -0
  48. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/constants.py +0 -0
  49. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/__init__.py +0 -0
  50. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/constants.py +0 -0
  51. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/enums.py +0 -0
  52. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/env_processor.py +0 -0
  53. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/environment_helpers.py +0 -0
  54. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/ergonomics.py +0 -0
  55. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/exceptions.py +0 -0
  56. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/graph_models.py +0 -0
  57. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/internal.py +0 -0
  58. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/models/__init__.py +0 -0
  59. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/models/build_environment.py +0 -0
  60. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/models/core.py +0 -0
  61. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/models/datasource.py +0 -0
  62. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/models/environment.py +0 -0
  63. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/optimization.py +0 -0
  64. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/optimizations/__init__.py +0 -0
  65. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/optimizations/base_optimization.py +0 -0
  66. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/optimizations/inline_datasource.py +0 -0
  67. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
  68. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/processing/__init__.py +0 -0
  69. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/processing/concept_strategies_v3.py +0 -0
  70. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/processing/graph_utils.py +0 -0
  71. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/processing/node_generators/__init__.py +0 -0
  72. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/processing/node_generators/basic_node.py +0 -0
  73. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/processing/node_generators/common.py +0 -0
  74. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/processing/node_generators/filter_node.py +0 -0
  75. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/processing/node_generators/group_node.py +0 -0
  76. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
  77. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
  78. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
  79. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
  80. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
  81. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +0 -0
  82. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
  83. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/processing/node_generators/select_node.py +0 -0
  84. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/processing/node_generators/synonym_node.py +0 -0
  85. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/processing/node_generators/union_node.py +0 -0
  86. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
  87. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/processing/node_generators/window_node.py +0 -0
  88. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/processing/nodes/__init__.py +0 -0
  89. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/processing/nodes/base_node.py +0 -0
  90. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/processing/nodes/filter_node.py +0 -0
  91. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/processing/nodes/group_node.py +0 -0
  92. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/processing/nodes/merge_node.py +0 -0
  93. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
  94. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/processing/nodes/union_node.py +0 -0
  95. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  96. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/processing/nodes/window_node.py +0 -0
  97. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/processing/utility.py +0 -0
  98. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/query_processor.py +0 -0
  99. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/statements/__init__.py +0 -0
  100. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/statements/author.py +0 -0
  101. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/statements/build.py +0 -0
  102. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/statements/common.py +0 -0
  103. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/core/statements/execute.py +0 -0
  104. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/dialect/__init__.py +0 -0
  105. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/dialect/base.py +0 -0
  106. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/dialect/bigquery.py +0 -0
  107. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/dialect/common.py +0 -0
  108. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/dialect/config.py +0 -0
  109. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/dialect/dataframe.py +0 -0
  110. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/dialect/duckdb.py +0 -0
  111. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/dialect/enums.py +0 -0
  112. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/dialect/postgres.py +0 -0
  113. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/dialect/presto.py +0 -0
  114. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/dialect/snowflake.py +0 -0
  115. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/dialect/sql_server.py +0 -0
  116. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/engine.py +0 -0
  117. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/executor.py +0 -0
  118. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/hooks/__init__.py +0 -0
  119. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/hooks/base_hook.py +0 -0
  120. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/hooks/graph_hook.py +0 -0
  121. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/hooks/query_debugger.py +0 -0
  122. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/metadata/__init__.py +0 -0
  123. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/parser.py +0 -0
  124. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/parsing/__init__.py +0 -0
  125. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/parsing/config.py +0 -0
  126. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/parsing/exceptions.py +0 -0
  127. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/parsing/helpers.py +0 -0
  128. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/parsing/render.py +0 -0
  129. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/parsing/trilogy.lark +0 -0
  130. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/py.typed +0 -0
  131. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/render.py +0 -0
  132. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/scripts/__init__.py +0 -0
  133. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/scripts/trilogy.py +0 -0
  134. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/std/__init__.py +0 -0
  135. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/std/date.preql +0 -0
  136. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/std/display.preql +0 -0
  137. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/std/geography.preql +0 -0
  138. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/std/money.preql +0 -0
  139. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/std/report.preql +0 -0
  140. {pytrilogy-0.0.3.51 → pytrilogy-0.0.3.52}/trilogy/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.51
3
+ Version: 0.0.3.52
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.51
3
+ Version: 0.0.3.52
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -92,7 +92,12 @@ def test_arg_to_datatype():
92
92
 
93
93
 
94
94
  def test_argument_to_purpose(test_environment: Environment):
95
- assert argument_to_purpose(1.00) == Purpose.CONSTANT
95
+ assert (
96
+ argument_to_purpose(
97
+ 1.00,
98
+ )
99
+ == Purpose.CONSTANT
100
+ )
96
101
  assert argument_to_purpose("test") == Purpose.CONSTANT
97
102
  assert argument_to_purpose(test_environment.concepts["order_id"]) == Purpose.KEY
98
103
  assert (
@@ -100,19 +105,22 @@ def test_argument_to_purpose(test_environment: Environment):
100
105
  [
101
106
  "test",
102
107
  1.00,
103
- ]
108
+ ],
109
+ test_environment,
104
110
  )
105
111
  == Purpose.CONSTANT
106
112
  )
107
113
  assert (
108
114
  function_args_to_output_purpose(
109
- ["test", 1.00, test_environment.concepts["order_id"]]
115
+ ["test", 1.00, test_environment.concepts["order_id"]], test_environment
110
116
  )
111
117
  == Purpose.PROPERTY
112
118
  )
113
119
  unnest_env, parsed = parse_text("const random <- unnest([1,2,3,4]);")
114
120
  assert (
115
- function_args_to_output_purpose([unnest_env.concepts["random"]])
121
+ function_args_to_output_purpose(
122
+ [unnest_env.concepts["random"]], test_environment
123
+ )
116
124
  == Purpose.PROPERTY
117
125
  )
118
126
 
@@ -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.51"
7
+ __version__ = "0.0.3.52"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -809,13 +809,14 @@ def create_function_derived_concept(
809
809
  namespace: str,
810
810
  operator: FunctionType,
811
811
  arguments: list[Concept],
812
+ environment: Environment,
812
813
  output_type: Optional[
813
814
  DataType | ListType | StructType | MapType | NumericType | TraitDataType
814
815
  ] = None,
815
816
  output_purpose: Optional[Purpose] = None,
816
817
  ) -> Concept:
817
818
  purpose = (
818
- function_args_to_output_purpose(arguments)
819
+ function_args_to_output_purpose(arguments, environment=environment)
819
820
  if output_purpose is None
820
821
  else output_purpose
821
822
  )
@@ -868,13 +869,15 @@ def argument_to_purpose(arg) -> Purpose:
868
869
  raise ValueError(f"Cannot parse arg purpose for {arg} of type {type(arg)}")
869
870
 
870
871
 
871
- def function_args_to_output_purpose(args) -> Purpose:
872
+ def function_args_to_output_purpose(args, environment: Environment) -> Purpose:
872
873
  has_metric = False
873
874
  has_non_constant = False
874
875
  has_non_single_row_constant = False
875
876
  if not args:
876
877
  return Purpose.CONSTANT
877
878
  for arg in args:
879
+ if isinstance(arg, ConceptRef):
880
+ arg = environment.concepts[arg.address]
878
881
  purpose = argument_to_purpose(arg)
879
882
  if purpose == Purpose.METRIC:
880
883
  has_metric = True
@@ -25,6 +25,7 @@ from pydantic import (
25
25
  ValidationInfo,
26
26
  computed_field,
27
27
  field_validator,
28
+ model_validator,
28
29
  )
29
30
 
30
31
  from trilogy.constants import DEFAULT_NAMESPACE, MagicConstants
@@ -621,8 +622,8 @@ class Comparison(ConceptArgs, Mergeable, DataTyped, Namespaced, BaseModel):
621
622
  return v.reference
622
623
  return v
623
624
 
624
- def __init__(self, *args, **kwargs) -> None:
625
- super().__init__(*args, **kwargs)
625
+ @model_validator(mode="after")
626
+ def validate_comparison(self):
626
627
  if self.operator in (ComparisonOperator.IS, ComparisonOperator.IS_NOT):
627
628
  if self.right != MagicConstants.NULL and DataType.BOOL != arg_to_datatype(
628
629
  self.right
@@ -632,7 +633,6 @@ class Comparison(ConceptArgs, Mergeable, DataTyped, Namespaced, BaseModel):
632
633
  )
633
634
  elif self.operator in (ComparisonOperator.IN, ComparisonOperator.NOT_IN):
634
635
  right_type = arg_to_datatype(self.right)
635
-
636
636
  if isinstance(right_type, ListType) and not is_compatible_datatype(
637
637
  arg_to_datatype(self.left), right_type.value_data_type
638
638
  ):
@@ -653,6 +653,8 @@ class Comparison(ConceptArgs, Mergeable, DataTyped, Namespaced, BaseModel):
653
653
  f"Cannot compare {arg_to_datatype(self.left)} and {arg_to_datatype(self.right)} of different types with operator {self.operator} in {str(self)}"
654
654
  )
655
655
 
656
+ return self
657
+
656
658
  def __add__(self, other):
657
659
  if other is None:
658
660
  return self
@@ -1022,7 +1024,7 @@ class Concept(Addressable, DataTyped, ConceptArgs, Mergeable, Namespaced, BaseMo
1022
1024
  keys = self.keys
1023
1025
 
1024
1026
  if self.is_aggregate and isinstance(new_lineage, Function) and grain.components:
1025
- grain_components = [
1027
+ grain_components: list[ConceptRef | Concept] = [
1026
1028
  environment.concepts[c].reference for c in grain.components
1027
1029
  ]
1028
1030
  new_lineage = AggregateWrapper(function=new_lineage, by=grain_components)
@@ -1847,9 +1849,6 @@ class AggregateWrapper(Mergeable, DataTyped, ConceptArgs, Namespaced, BaseModel)
1847
1849
  function: Function
1848
1850
  by: List[ConceptRef | Concept] = Field(default_factory=list)
1849
1851
 
1850
- def __init__(self, **kwargs):
1851
- super().__init__(**kwargs)
1852
-
1853
1852
  @field_validator("by", mode="before")
1854
1853
  @classmethod
1855
1854
  def enforce_concept_ref(cls, v):
@@ -262,11 +262,8 @@ class BuildGrain(BaseModel):
262
262
  components: set[str] = Field(default_factory=set)
263
263
  where_clause: Optional[BuildWhereClause] = None
264
264
 
265
- def __init__(self, **kwargs):
266
- super().__init__(**kwargs)
267
-
268
265
  def without_condition(self):
269
- return BuildGrain(components=self.components)
266
+ return BuildGrain.model_construct(components=self.components)
270
267
 
271
268
  @classmethod
272
269
  def from_concepts(
@@ -321,12 +318,12 @@ class BuildGrain(BaseModel):
321
318
  # raise NotImplementedError(
322
319
  # f"Cannot merge grains with where clauses, self {self.where_clause} other {other.where_clause}"
323
320
  # )
324
- return BuildGrain(
321
+ return BuildGrain.model_construct(
325
322
  components=self.components.union(other.components), where_clause=where
326
323
  )
327
324
 
328
325
  def __sub__(self, other: "BuildGrain") -> "BuildGrain":
329
- return BuildGrain(
326
+ return BuildGrain.model_construct(
330
327
  components=self.components.difference(other.components),
331
328
  where_clause=self.where_clause,
332
329
  )
@@ -637,9 +634,6 @@ class BuildComparison(BuildConceptArgs, ConstantInlineable, BaseModel):
637
634
  ]
638
635
  operator: ComparisonOperator
639
636
 
640
- def __init__(self, *args, **kwargs) -> None:
641
- super().__init__(*args, **kwargs)
642
-
643
637
  def __add__(self, other):
644
638
  if other is None:
645
639
  return self
@@ -1,9 +1,16 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from collections import defaultdict
4
- from typing import Any, Dict, List, Optional, Set, Union
5
-
6
- from pydantic import BaseModel, Field, ValidationInfo, computed_field, field_validator
4
+ from typing import Dict, List, Optional, Set, Union
5
+
6
+ from pydantic import (
7
+ BaseModel,
8
+ Field,
9
+ ValidationInfo,
10
+ computed_field,
11
+ field_validator,
12
+ model_validator,
13
+ )
7
14
 
8
15
  from trilogy.constants import CONFIG, logger
9
16
  from trilogy.core.constants import CONSTANT_DATASET
@@ -473,8 +480,8 @@ class BaseJoin(BaseModel):
473
480
  left_datasource: Optional[Union[BuildDatasource, "QueryDatasource"]] = None
474
481
  concept_pairs: list[ConceptPair] | None = None
475
482
 
476
- def __init__(self, **data: Any):
477
- super().__init__(**data)
483
+ @model_validator(mode="after")
484
+ def validate_join(self) -> "BaseJoin":
478
485
  if (
479
486
  self.left_datasource
480
487
  and self.left_datasource.identifier == self.right_datasource.identifier
@@ -483,14 +490,18 @@ class BaseJoin(BaseModel):
483
490
  f"Cannot join a dataself to itself, joining {self.left_datasource} and"
484
491
  f" {self.right_datasource}"
485
492
  )
486
- final_concepts = []
487
493
 
488
- # if we have a list of concept pairs
494
+ # Early returns maintained as in original code
489
495
  if self.concept_pairs:
490
- return
496
+ return self
497
+
491
498
  if self.concepts == []:
492
- return
499
+ return self
500
+
501
+ # Validation logic
502
+ final_concepts = []
493
503
  assert self.left_datasource and self.right_datasource
504
+
494
505
  for concept in self.concepts or []:
495
506
  include = True
496
507
  for ds in [self.left_datasource, self.right_datasource]:
@@ -507,6 +518,7 @@ class BaseJoin(BaseModel):
507
518
  )
508
519
  if include:
509
520
  final_concepts.append(concept)
521
+
510
522
  if not final_concepts and self.concepts:
511
523
  # if one datasource only has constants
512
524
  # we can join on 1=1
@@ -519,11 +531,11 @@ class BaseJoin(BaseModel):
519
531
  ]
520
532
  ):
521
533
  self.concepts = []
522
- return
534
+ return self
523
535
  # if everything is at abstract grain, we can skip joins
524
536
  if all([c.grain.abstract for c in ds.output_concepts]):
525
537
  self.concepts = []
526
- return
538
+ return self
527
539
 
528
540
  left_keys = [c.address for c in self.left_datasource.output_concepts]
529
541
  right_keys = [c.address for c in self.right_datasource.output_concepts]
@@ -535,7 +547,9 @@ class BaseJoin(BaseModel):
535
547
  f" right_keys {right_keys},"
536
548
  f" provided join concepts {match_concepts}"
537
549
  )
550
+
538
551
  self.concepts = final_concepts
552
+ return self
539
553
 
540
554
  @property
541
555
  def unique_id(self) -> str:
@@ -62,7 +62,7 @@ def process_function_arg(
62
62
  operator=FunctionType.PARENTHETICAL,
63
63
  arguments=processed,
64
64
  output_datatype=arg_to_datatype(processed[0]),
65
- output_purpose=function_args_to_output_purpose(processed),
65
+ output_purpose=function_args_to_output_purpose(processed, environment),
66
66
  )
67
67
  elif isinstance(arg, Function):
68
68
  # if it's not an aggregate function, we can skip the virtual concepts
@@ -140,7 +140,7 @@ def get_purpose_and_keys(
140
140
  args: Tuple[ConceptRef | Concept, ...] | None,
141
141
  environment: Environment,
142
142
  ) -> Tuple[Purpose, set[str] | None]:
143
- local_purpose = purpose or function_args_to_output_purpose(args)
143
+ local_purpose = purpose or function_args_to_output_purpose(args, environment)
144
144
  if local_purpose in (Purpose.PROPERTY, Purpose.METRIC) and args:
145
145
  keys = concept_list_to_keys(args, environment)
146
146
  else:
@@ -548,7 +548,7 @@ def filter_item_to_concept(
548
548
 
549
549
  else:
550
550
  raise NotImplementedError(
551
- f"Filter item with non ref content {parent.content} not yet supported"
551
+ f"Filter item with non ref content {parent.content} ({type(parent.content)}) not yet supported"
552
552
  )
553
553
  modifiers = get_upstream_modifiers(
554
554
  cparent.concept_arguments, environment=environment
@@ -1257,16 +1257,21 @@ class ParseToObjects(Transformer):
1257
1257
  intersection = base.locally_derived.intersection(pre_keys)
1258
1258
  if intersection:
1259
1259
  for x in intersection:
1260
- if (
1261
- base.local_concepts[x].derivation
1262
- == self.environment.concepts[x].derivation
1260
+ if str(base.local_concepts[x].lineage) == str(
1261
+ self.environment.concepts[x].lineage
1263
1262
  ):
1263
+ local = base.local_concepts[x]
1264
+ friendly_name = (
1265
+ local.name
1266
+ if local.namespace == DEFAULT_NAMESPACE
1267
+ else local.namespace
1268
+ )
1264
1269
  raise NameShadowError(
1265
- f"Select statement {base} derives concept {x} with identical derivation as named concept. Use the named concept directly."
1270
+ f"Select statement {base} creates a new concept '{friendly_name}' with identical definition as the existing concept '{friendly_name}'. Replace {base.local_concepts[x].lineage} with a direct reference to {friendly_name}."
1266
1271
  )
1267
1272
  else:
1268
1273
  raise NameShadowError(
1269
- f"Select statement {base} creates new derived concepts {list(intersection)} with identical name(s) to existing concept(s). If these are identical, reference the concept directly. Otherwise alias your column as a new name."
1274
+ f"Select statement {base} creates new named concepts from calculations {list(intersection)} with identical name(s) to existing concept(s). Use new unique names for these."
1270
1275
  )
1271
1276
  return base
1272
1277
 
File without changes
File without changes
File without changes
File without changes