pytrilogy 0.0.3.34__tar.gz → 0.0.3.36__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.34/pytrilogy.egg-info → pytrilogy-0.0.3.36}/PKG-INFO +1 -1
  2. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36/pytrilogy.egg-info}/PKG-INFO +1 -1
  3. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/pytrilogy.egg-info/SOURCES.txt +2 -2
  4. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/tests/test_derived_concepts.py +8 -0
  5. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/tests/test_metadata.py +13 -13
  6. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/__init__.py +1 -1
  7. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/environment_helpers.py +2 -2
  8. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/functions.py +17 -4
  9. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/models/author.py +20 -4
  10. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/models/build.py +35 -4
  11. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/statements/author.py +0 -1
  12. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/parsing/common.py +1 -1
  13. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/parsing/parse_engine.py +15 -12
  14. pytrilogy-0.0.3.36/trilogy/std/report.preql +5 -0
  15. pytrilogy-0.0.3.34/trilogy/std/dashboard.preql +0 -5
  16. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/LICENSE.md +0 -0
  17. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/README.md +0 -0
  18. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/pyproject.toml +0 -0
  19. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/pytrilogy.egg-info/dependency_links.txt +0 -0
  20. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/pytrilogy.egg-info/entry_points.txt +0 -0
  21. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/pytrilogy.egg-info/requires.txt +0 -0
  22. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/pytrilogy.egg-info/top_level.txt +0 -0
  23. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/setup.cfg +0 -0
  24. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/setup.py +0 -0
  25. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/tests/test_datatypes.py +0 -0
  26. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/tests/test_declarations.py +0 -0
  27. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/tests/test_discovery_nodes.py +0 -0
  28. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/tests/test_enums.py +0 -0
  29. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/tests/test_environment.py +0 -0
  30. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/tests/test_executor.py +0 -0
  31. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/tests/test_functions.py +0 -0
  32. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/tests/test_imports.py +0 -0
  33. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/tests/test_models.py +0 -0
  34. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/tests/test_multi_join_assignments.py +0 -0
  35. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/tests/test_parse_engine.py +0 -0
  36. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/tests/test_parsing.py +0 -0
  37. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/tests/test_partial_handling.py +0 -0
  38. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/tests/test_query_processing.py +0 -0
  39. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/tests/test_query_render.py +0 -0
  40. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/tests/test_select.py +0 -0
  41. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/tests/test_show.py +0 -0
  42. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/tests/test_statements.py +0 -0
  43. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/tests/test_typing.py +0 -0
  44. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/tests/test_undefined_concept.py +0 -0
  45. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/tests/test_user_functions.py +0 -0
  46. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/tests/test_where_clause.py +0 -0
  47. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/authoring/__init__.py +0 -0
  48. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/compiler.py +0 -0
  49. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/constants.py +0 -0
  50. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/__init__.py +0 -0
  51. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/constants.py +0 -0
  52. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/enums.py +0 -0
  53. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/env_processor.py +0 -0
  54. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/ergonomics.py +0 -0
  55. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/exceptions.py +0 -0
  56. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/graph_models.py +0 -0
  57. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/internal.py +0 -0
  58. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/models/__init__.py +0 -0
  59. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/models/build_environment.py +0 -0
  60. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/models/core.py +0 -0
  61. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/models/datasource.py +0 -0
  62. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/models/environment.py +0 -0
  63. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/models/execute.py +0 -0
  64. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/optimization.py +0 -0
  65. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/optimizations/__init__.py +0 -0
  66. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/optimizations/base_optimization.py +0 -0
  67. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/optimizations/inline_constant.py +0 -0
  68. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/optimizations/inline_datasource.py +0 -0
  69. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
  70. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/processing/__init__.py +0 -0
  71. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/processing/concept_strategies_v3.py +0 -0
  72. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/processing/graph_utils.py +0 -0
  73. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/processing/node_generators/__init__.py +0 -0
  74. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/processing/node_generators/basic_node.py +0 -0
  75. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/processing/node_generators/common.py +0 -0
  76. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/processing/node_generators/filter_node.py +0 -0
  77. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/processing/node_generators/group_node.py +0 -0
  78. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
  79. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
  80. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
  81. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
  82. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
  83. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +0 -0
  84. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
  85. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/processing/node_generators/select_node.py +0 -0
  86. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/processing/node_generators/synonym_node.py +0 -0
  87. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/processing/node_generators/union_node.py +0 -0
  88. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
  89. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/processing/node_generators/window_node.py +0 -0
  90. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/processing/nodes/__init__.py +0 -0
  91. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/processing/nodes/base_node.py +0 -0
  92. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/processing/nodes/filter_node.py +0 -0
  93. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/processing/nodes/group_node.py +0 -0
  94. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/processing/nodes/merge_node.py +0 -0
  95. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
  96. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/processing/nodes/union_node.py +0 -0
  97. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  98. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/processing/nodes/window_node.py +0 -0
  99. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/processing/utility.py +0 -0
  100. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/query_processor.py +0 -0
  101. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/statements/__init__.py +0 -0
  102. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/statements/build.py +0 -0
  103. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/statements/common.py +0 -0
  104. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/core/statements/execute.py +0 -0
  105. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/dialect/__init__.py +0 -0
  106. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/dialect/base.py +0 -0
  107. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/dialect/bigquery.py +0 -0
  108. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/dialect/common.py +0 -0
  109. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/dialect/config.py +0 -0
  110. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/dialect/dataframe.py +0 -0
  111. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/dialect/duckdb.py +0 -0
  112. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/dialect/enums.py +0 -0
  113. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/dialect/postgres.py +0 -0
  114. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/dialect/presto.py +0 -0
  115. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/dialect/snowflake.py +0 -0
  116. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/dialect/sql_server.py +0 -0
  117. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/engine.py +0 -0
  118. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/executor.py +0 -0
  119. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/hooks/__init__.py +0 -0
  120. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/hooks/base_hook.py +0 -0
  121. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/hooks/graph_hook.py +0 -0
  122. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/hooks/query_debugger.py +0 -0
  123. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/metadata/__init__.py +0 -0
  124. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/parser.py +0 -0
  125. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/parsing/__init__.py +0 -0
  126. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/parsing/config.py +0 -0
  127. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/parsing/exceptions.py +0 -0
  128. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/parsing/helpers.py +0 -0
  129. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/parsing/render.py +0 -0
  130. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/parsing/trilogy.lark +0 -0
  131. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/py.typed +0 -0
  132. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/render.py +0 -0
  133. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/scripts/__init__.py +0 -0
  134. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/scripts/trilogy.py +0 -0
  135. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/std/__init__.py +0 -0
  136. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/std/date.preql +0 -0
  137. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/std/display.preql +0 -0
  138. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/std/geography.preql +0 -0
  139. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/std/money.preql +0 -0
  140. {pytrilogy-0.0.3.34 → pytrilogy-0.0.3.36}/trilogy/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.34
3
+ Version: 0.0.3.36
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.34
3
+ Version: 0.0.3.36
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -130,8 +130,8 @@ trilogy/parsing/trilogy.lark
130
130
  trilogy/scripts/__init__.py
131
131
  trilogy/scripts/trilogy.py
132
132
  trilogy/std/__init__.py
133
- trilogy/std/dashboard.preql
134
133
  trilogy/std/date.preql
135
134
  trilogy/std/display.preql
136
135
  trilogy/std/geography.preql
137
- trilogy/std/money.preql
136
+ trilogy/std/money.preql
137
+ trilogy/std/report.preql
@@ -11,6 +11,14 @@ def test_derivations(test_environment):
11
11
  )
12
12
 
13
13
 
14
+ def test_hour_derivation(test_environment):
15
+ hour_derived = test_environment.concepts["order_timestamp.hour"]
16
+ assert "hour" in hour_derived.datatype.traits, hour_derived
17
+
18
+ year_derived = test_environment.concepts["order_timestamp.year"]
19
+ assert "year" in year_derived.datatype.traits, year_derived
20
+
21
+
14
22
  def test_filtering_where_on_derived_aggregate(test_environment):
15
23
  exception = False
16
24
  try:
@@ -14,16 +14,16 @@ property user_id.display_name string metadata(description="The display name");""
14
14
  assert env.concepts["display_name"].metadata.description == "The display name"
15
15
 
16
16
 
17
- def test_import_metadata():
18
- env = Environment(working_path=Path(__file__).parent)
19
- env, _ = parse(
20
- """import test_env as env; # Dragon metrics
21
- import test_env as env2;""",
22
- environment=env,
23
- )
24
-
25
- assert "Dragon metrics" in env.concepts["env.id"].metadata.description
26
-
27
- env2 = env.concepts["env2.id"]
28
- assert env2.namespace == "env2"
29
- assert env.concepts["env2.id"].metadata.description is None
17
+ # def test_import_metadata():
18
+ # env = Environment(working_path=Path(__file__).parent)
19
+ # env, _ = parse(
20
+ # """import test_env as env; # Dragon metrics
21
+ # import test_env as env2;""",
22
+ # environment=env,
23
+ # )
24
+
25
+ # assert "Dragon metrics" in env.concepts["env.id"].metadata.description
26
+
27
+ # env2 = env.concepts["env2.id"]
28
+ # assert env2.namespace == "env2"
29
+ # assert env.concepts["env2.id"].metadata.description is None
@@ -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.34"
7
+ __version__ = "0.0.3.36"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -47,7 +47,7 @@ def generate_date_concepts(concept: Concept, environment: Environment):
47
47
  )
48
48
  new_concept = Concept(
49
49
  name=f"{concept.name}.{fname}",
50
- datatype=DataType.INTEGER,
50
+ datatype=function.output_datatype,
51
51
  purpose=default_type,
52
52
  lineage=function,
53
53
  grain=concept.grain,
@@ -119,7 +119,7 @@ def generate_datetime_concepts(concept: Concept, environment: Environment):
119
119
  )
120
120
  new_concept = Concept(
121
121
  name=f"{concept.name}.{fname}",
122
- datatype=DataType.INTEGER,
122
+ datatype=const_function.output_datatype,
123
123
  purpose=default_type,
124
124
  lineage=const_function,
125
125
  grain=concept.grain,
@@ -111,11 +111,14 @@ def validate_case_output(
111
111
  if output_datatype != DataType.NULL:
112
112
  datatypes.add(output_datatype.data_type)
113
113
  mapz[str(arg.expr)] = output_datatype
114
- if not len(datatypes) == 1:
114
+ known = [x for x in datatypes if x != DataType.UNKNOWN]
115
+ if len(known) == 0:
116
+ return DataType.UNKNOWN
117
+ if not len(known) == 1:
115
118
  raise SyntaxError(
116
119
  f"All case expressions must have the same output datatype, got {datatypes} from {mapz}"
117
120
  )
118
- return datatypes.pop()
121
+ return known.pop()
119
122
 
120
123
 
121
124
  def create_struct_output(
@@ -672,12 +675,22 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
672
675
  arg_count=1,
673
676
  ),
674
677
  FunctionType.SUM: FunctionConfig(
675
- valid_inputs={DataType.INTEGER, DataType.FLOAT, DataType.NUMBER},
678
+ valid_inputs={
679
+ DataType.INTEGER,
680
+ DataType.FLOAT,
681
+ DataType.NUMBER,
682
+ DataType.NUMERIC,
683
+ },
676
684
  output_purpose=Purpose.METRIC,
677
685
  arg_count=1,
678
686
  ),
679
687
  FunctionType.AVG: FunctionConfig(
680
- valid_inputs={DataType.INTEGER, DataType.FLOAT, DataType.NUMBER},
688
+ valid_inputs={
689
+ DataType.INTEGER,
690
+ DataType.FLOAT,
691
+ DataType.NUMBER,
692
+ DataType.NUMERIC,
693
+ },
681
694
  output_purpose=Purpose.METRIC,
682
695
  arg_count=1,
683
696
  ),
@@ -107,6 +107,10 @@ class ConceptRef(Addressable, Namespaced, DataTyped, Mergeable, BaseModel):
107
107
  ) = DataType.UNKNOWN
108
108
  metadata: Optional["Metadata"] = None
109
109
 
110
+ @property
111
+ def reference(self):
112
+ return self
113
+
110
114
  @property
111
115
  def line_no(self) -> int | None:
112
116
  if self.metadata:
@@ -1426,7 +1430,7 @@ def get_basic_type(
1426
1430
  return type
1427
1431
 
1428
1432
 
1429
- class CaseWhen(Namespaced, ConceptArgs, Mergeable, BaseModel):
1433
+ class CaseWhen(Namespaced, DataTyped, ConceptArgs, Mergeable, BaseModel):
1430
1434
  comparison: Conditional | SubselectComparison | Comparison
1431
1435
  expr: "Expr"
1432
1436
 
@@ -1436,6 +1440,10 @@ class CaseWhen(Namespaced, ConceptArgs, Mergeable, BaseModel):
1436
1440
  return v.reference
1437
1441
  return v
1438
1442
 
1443
+ @property
1444
+ def output_datatype(self):
1445
+ return arg_to_datatype(self.expr)
1446
+
1439
1447
  def __str__(self):
1440
1448
  return self.__repr__()
1441
1449
 
@@ -1488,7 +1496,7 @@ class CaseWhen(Namespaced, ConceptArgs, Mergeable, BaseModel):
1488
1496
  )
1489
1497
 
1490
1498
 
1491
- class CaseElse(Namespaced, ConceptArgs, Mergeable, BaseModel):
1499
+ class CaseElse(Namespaced, ConceptArgs, DataTyped, Mergeable, BaseModel):
1492
1500
  expr: "Expr"
1493
1501
  # this ensures that it's easily differentiable from CaseWhen
1494
1502
  discriminant: ComparisonOperator = ComparisonOperator.ELSE
@@ -1499,6 +1507,10 @@ class CaseElse(Namespaced, ConceptArgs, Mergeable, BaseModel):
1499
1507
  def __repr__(self):
1500
1508
  return f"ELSE {str(self.expr)}"
1501
1509
 
1510
+ @property
1511
+ def output_datatype(self):
1512
+ return arg_to_datatype(self.expr)
1513
+
1502
1514
  @field_validator("expr", mode="before")
1503
1515
  def enforce_expr(cls, v):
1504
1516
  if isinstance(v, Concept):
@@ -1837,7 +1849,7 @@ class FunctionCallWrapper(
1837
1849
 
1838
1850
  class AggregateWrapper(Mergeable, DataTyped, ConceptArgs, Namespaced, BaseModel):
1839
1851
  function: Function
1840
- by: List[ConceptRef] = Field(default_factory=list)
1852
+ by: List[ConceptRef | Concept] = Field(default_factory=list)
1841
1853
 
1842
1854
  def __init__(self, **kwargs):
1843
1855
  super().__init__(**kwargs)
@@ -1863,7 +1875,7 @@ class AggregateWrapper(Mergeable, DataTyped, ConceptArgs, Namespaced, BaseModel)
1863
1875
 
1864
1876
  @property
1865
1877
  def concept_arguments(self) -> List[ConceptRef]:
1866
- return self.function.concept_arguments + self.by
1878
+ return self.function.concept_arguments + [x.reference for x in self.by]
1867
1879
 
1868
1880
  @property
1869
1881
  def output_datatype(self):
@@ -2392,4 +2404,8 @@ FuncArgs = (
2392
2404
  | NumericType
2393
2405
  | list
2394
2406
  | ListWrapper[Any]
2407
+ | TupleWrapper[Any]
2408
+ | Comparison
2409
+ | Conditional
2410
+ | MagicConstants
2395
2411
  )
@@ -1538,9 +1538,23 @@ class Factory:
1538
1538
 
1539
1539
  @build.register
1540
1540
  def _(self, base: CaseWhen) -> BuildCaseWhen:
1541
+ from trilogy.parsing.common import arbitrary_to_concept
1542
+
1543
+ comparison = base.comparison
1544
+ if isinstance(comparison, (AggregateWrapper, FilterItem, WindowItem)):
1545
+ comparison = arbitrary_to_concept(
1546
+ comparison,
1547
+ environment=self.environment,
1548
+ )
1549
+ expr: Concept | FuncArgs = base.expr
1550
+ if isinstance(expr, (AggregateWrapper, FilterItem, WindowItem)):
1551
+ expr = arbitrary_to_concept(
1552
+ expr,
1553
+ environment=self.environment,
1554
+ )
1541
1555
  return BuildCaseWhen.model_construct(
1542
- comparison=self.build(base.comparison),
1543
- expr=(self.build(base.expr)),
1556
+ comparison=self.build(comparison),
1557
+ expr=self.build(expr),
1544
1558
  )
1545
1559
 
1546
1560
  @build.register
@@ -1647,10 +1661,27 @@ class Factory:
1647
1661
  @build.register
1648
1662
  def _(self, base: WindowItem) -> BuildWindowItem:
1649
1663
  # to do proper discovery, we need to inject virtual intermediate ocncepts
1664
+ from trilogy.parsing.common import arbitrary_to_concept
1665
+
1666
+ content: Concept | FuncArgs = base.content
1667
+ if isinstance(content, (AggregateWrapper, FilterItem, WindowItem)):
1668
+ content = arbitrary_to_concept(
1669
+ content,
1670
+ environment=self.environment,
1671
+ )
1672
+ final_by = []
1673
+ for x in base.order_by:
1674
+ if (
1675
+ isinstance(x.expr, AggregateWrapper)
1676
+ and not x.expr.by
1677
+ and isinstance(content, (ConceptRef, Concept))
1678
+ ):
1679
+ x.expr.by = [content]
1680
+ final_by.append(x)
1650
1681
  return BuildWindowItem.model_construct(
1651
1682
  type=base.type,
1652
- content=self.build(base.content),
1653
- order_by=[self.build(x) for x in base.order_by],
1683
+ content=self.build(content),
1684
+ order_by=[self.build(x) for x in final_by],
1654
1685
  over=[self.build(x) for x in base.over],
1655
1686
  index=base.index,
1656
1687
  )
@@ -135,7 +135,6 @@ class SelectStatement(HasUUID, SelectTypeMixin, BaseModel):
135
135
  )
136
136
 
137
137
  output.grain = output.calculate_grain(environment)
138
-
139
138
  for x in selection:
140
139
  if x.is_undefined and environment.concepts.fail_on_missing:
141
140
  environment.concepts.raise_undefined(
@@ -270,7 +270,7 @@ def _get_relevant_parent_concepts(arg) -> tuple[list[ConceptRef], bool]:
270
270
  elif isinstance(arg, AggregateWrapper) and not arg.by:
271
271
  return [], True
272
272
  elif isinstance(arg, AggregateWrapper) and arg.by:
273
- return arg.by, True
273
+ return [x.reference for x in arg.by], True
274
274
  elif isinstance(arg, FunctionCallWrapper):
275
275
  return get_relevant_parent_concepts(arg.content)
276
276
  return get_concept_arguments(arg), False
@@ -368,18 +368,21 @@ class ParseToObjects(Transformer):
368
368
  output.concept.metadata.description
369
369
  or args[1].text.split("#")[1].strip()
370
370
  )
371
- if isinstance(output, ImportStatement):
372
- if len(args) > 1 and isinstance(args[1], Comment):
373
- comment = args[1].text.split("#")[1].strip()
374
- namespace = output.alias
375
- for _, v in self.environment.concepts.items():
376
- if v.namespace == namespace:
377
- if v.metadata.description:
378
- v.metadata.description = (
379
- f"{comment}: {v.metadata.description}"
380
- )
381
- else:
382
- v.metadata.description = comment
371
+ # this is a bad plan for now;
372
+ # because a comment after an import statement is very common
373
+ # and it's not intuitive that it modifies the import description
374
+ # if isinstance(output, ImportStatement):
375
+ # if len(args) > 1 and isinstance(args[1], Comment):
376
+ # comment = args[1].text.split("#")[1].strip()
377
+ # namespace = output.alias
378
+ # for _, v in self.environment.concepts.items():
379
+ # if v.namespace == namespace:
380
+ # if v.metadata.description:
381
+ # v.metadata.description = (
382
+ # f"{comment}: {v.metadata.description}"
383
+ # )
384
+ # else:
385
+ # v.metadata.description = comment
383
386
 
384
387
  return args[0]
385
388
 
@@ -0,0 +1,5 @@
1
+
2
+
3
+
4
+ def top_x_by_metric(val, metric, x=10, default='other') -> case when rank val by metric desc <x+1 then val else default end; # Get the top X of a value by a certain metric, bucket the rest in other
5
+
@@ -1,5 +0,0 @@
1
-
2
-
3
-
4
- # def top_x_by_metric(val, metric, x=10) -> case when rank val by metric order by metric desc <x then val else 'Other' end;
5
-
File without changes
File without changes
File without changes
File without changes