pytrilogy 0.0.3.23__tar.gz → 0.0.3.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 (133) hide show
  1. {pytrilogy-0.0.3.23/pytrilogy.egg-info → pytrilogy-0.0.3.25}/PKG-INFO +1 -1
  2. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25/pytrilogy.egg-info}/PKG-INFO +1 -1
  3. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/tests/test_parsing.py +4 -1
  4. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/tests/test_typing.py +71 -0
  5. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/__init__.py +1 -1
  6. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/environment_helpers.py +2 -2
  7. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/functions.py +0 -1
  8. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/models/author.py +20 -3
  9. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/models/build.py +9 -1
  10. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/models/core.py +18 -1
  11. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/models/environment.py +7 -0
  12. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/processing/utility.py +2 -0
  13. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/dialect/base.py +4 -0
  14. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/parsing/parse_engine.py +41 -16
  15. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/LICENSE.md +0 -0
  16. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/README.md +0 -0
  17. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/pyproject.toml +0 -0
  18. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/pytrilogy.egg-info/SOURCES.txt +0 -0
  19. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/pytrilogy.egg-info/dependency_links.txt +0 -0
  20. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/pytrilogy.egg-info/entry_points.txt +0 -0
  21. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/pytrilogy.egg-info/requires.txt +0 -0
  22. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/pytrilogy.egg-info/top_level.txt +0 -0
  23. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/setup.cfg +0 -0
  24. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/setup.py +0 -0
  25. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/tests/test_datatypes.py +0 -0
  26. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/tests/test_declarations.py +0 -0
  27. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/tests/test_derived_concepts.py +0 -0
  28. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/tests/test_discovery_nodes.py +0 -0
  29. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/tests/test_enums.py +0 -0
  30. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/tests/test_environment.py +0 -0
  31. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/tests/test_executor.py +0 -0
  32. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/tests/test_functions.py +0 -0
  33. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/tests/test_imports.py +0 -0
  34. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/tests/test_metadata.py +0 -0
  35. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/tests/test_models.py +0 -0
  36. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/tests/test_multi_join_assignments.py +0 -0
  37. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/tests/test_parse_engine.py +0 -0
  38. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/tests/test_partial_handling.py +0 -0
  39. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/tests/test_query_processing.py +0 -0
  40. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/tests/test_query_render.py +0 -0
  41. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/tests/test_select.py +0 -0
  42. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/tests/test_show.py +0 -0
  43. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/tests/test_statements.py +0 -0
  44. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/tests/test_undefined_concept.py +0 -0
  45. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/tests/test_user_functions.py +0 -0
  46. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/tests/test_where_clause.py +0 -0
  47. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/authoring/__init__.py +0 -0
  48. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/compiler.py +0 -0
  49. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/constants.py +0 -0
  50. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/__init__.py +0 -0
  51. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/constants.py +0 -0
  52. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/enums.py +0 -0
  53. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/env_processor.py +0 -0
  54. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/ergonomics.py +0 -0
  55. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/exceptions.py +0 -0
  56. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/graph_models.py +0 -0
  57. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/internal.py +0 -0
  58. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/models/__init__.py +0 -0
  59. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/models/build_environment.py +0 -0
  60. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/models/datasource.py +0 -0
  61. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/models/execute.py +0 -0
  62. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/optimization.py +0 -0
  63. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/optimizations/__init__.py +0 -0
  64. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/optimizations/base_optimization.py +0 -0
  65. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/optimizations/inline_constant.py +0 -0
  66. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/optimizations/inline_datasource.py +0 -0
  67. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/optimizations/predicate_pushdown.py +0 -0
  68. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/processing/__init__.py +0 -0
  69. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/processing/concept_strategies_v3.py +0 -0
  70. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/processing/graph_utils.py +0 -0
  71. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/processing/node_generators/__init__.py +0 -0
  72. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/processing/node_generators/basic_node.py +0 -0
  73. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/processing/node_generators/common.py +0 -0
  74. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/processing/node_generators/filter_node.py +0 -0
  75. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/processing/node_generators/group_node.py +0 -0
  76. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/processing/node_generators/group_to_node.py +0 -0
  77. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/processing/node_generators/multiselect_node.py +0 -0
  78. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/processing/node_generators/node_merge_node.py +0 -0
  79. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/processing/node_generators/rowset_node.py +0 -0
  80. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/processing/node_generators/select_helpers/__init__.py +0 -0
  81. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/processing/node_generators/select_helpers/datasource_injection.py +0 -0
  82. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/processing/node_generators/select_merge_node.py +0 -0
  83. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/processing/node_generators/select_node.py +0 -0
  84. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/processing/node_generators/synonym_node.py +0 -0
  85. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/processing/node_generators/union_node.py +0 -0
  86. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/processing/node_generators/unnest_node.py +0 -0
  87. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/processing/node_generators/window_node.py +0 -0
  88. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/processing/nodes/__init__.py +0 -0
  89. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/processing/nodes/base_node.py +0 -0
  90. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/processing/nodes/filter_node.py +0 -0
  91. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/processing/nodes/group_node.py +0 -0
  92. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/processing/nodes/merge_node.py +0 -0
  93. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/processing/nodes/select_node_v2.py +0 -0
  94. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/processing/nodes/union_node.py +0 -0
  95. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/processing/nodes/unnest_node.py +0 -0
  96. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/processing/nodes/window_node.py +0 -0
  97. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/query_processor.py +0 -0
  98. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/statements/__init__.py +0 -0
  99. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/statements/author.py +0 -0
  100. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/statements/build.py +0 -0
  101. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/statements/common.py +0 -0
  102. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/core/statements/execute.py +0 -0
  103. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/dialect/__init__.py +0 -0
  104. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/dialect/bigquery.py +0 -0
  105. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/dialect/common.py +0 -0
  106. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/dialect/config.py +0 -0
  107. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/dialect/dataframe.py +0 -0
  108. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/dialect/duckdb.py +0 -0
  109. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/dialect/enums.py +0 -0
  110. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/dialect/postgres.py +0 -0
  111. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/dialect/presto.py +0 -0
  112. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/dialect/snowflake.py +0 -0
  113. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/dialect/sql_server.py +0 -0
  114. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/engine.py +0 -0
  115. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/executor.py +0 -0
  116. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/hooks/__init__.py +0 -0
  117. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/hooks/base_hook.py +0 -0
  118. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/hooks/graph_hook.py +0 -0
  119. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/hooks/query_debugger.py +0 -0
  120. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/metadata/__init__.py +0 -0
  121. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/parser.py +0 -0
  122. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/parsing/__init__.py +0 -0
  123. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/parsing/common.py +0 -0
  124. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/parsing/config.py +0 -0
  125. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/parsing/exceptions.py +0 -0
  126. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/parsing/helpers.py +0 -0
  127. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/parsing/render.py +0 -0
  128. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/parsing/trilogy.lark +0 -0
  129. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/py.typed +0 -0
  130. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/render.py +0 -0
  131. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/scripts/__init__.py +0 -0
  132. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/scripts/trilogy.py +0 -0
  133. {pytrilogy-0.0.3.23 → pytrilogy-0.0.3.25}/trilogy/utility.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.23
3
+ Version: 0.0.3.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.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.23
3
+ Version: 0.0.3.25
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -657,7 +657,10 @@ key x int;
657
657
  assert isinstance(env.config.import_resolver, DictImportResolver)
658
658
  env.parse(
659
659
  """
660
- import test;
660
+ import std.geography;
661
+ import test;
662
+
663
+ key fun_lat float::latitude;
661
664
 
662
665
  select x % 10 -> x_mod_10;
663
666
 
@@ -1,6 +1,25 @@
1
1
  from decimal import Decimal
2
+ from pathlib import Path
3
+
4
+ from pytest import raises
2
5
 
3
6
  from trilogy import Dialects
7
+ from trilogy.parsing.exceptions import ParseError
8
+
9
+ FILE = Path(__file__)
10
+
11
+
12
+ def test_invalid_typing():
13
+ env = Dialects.DUCK_DB.default_executor()
14
+ with raises(ParseError):
15
+ env.environment.parse(
16
+ """
17
+ key customer_id int;
18
+ property customer_id.email string::email;
19
+
20
+
21
+ """
22
+ )
4
23
 
5
24
 
6
25
  def test_typing():
@@ -44,6 +63,58 @@ customer_id,
44
63
  assert env.environment.concepts["email"].datatype.traits == ["email"]
45
64
 
46
65
 
66
+ def test_type_import_and_cast():
67
+ env = Dialects.DUCK_DB.default_executor()
68
+ env.environment.add_file_import(FILE.parent / "test_env_types.preql", "dtypes")
69
+ env.environment.parse(
70
+ """
71
+
72
+ type year int;
73
+ key customer_id int;
74
+ property customer_id.signup_date date;
75
+
76
+ datasource customers (
77
+ id:customer_id,
78
+ signup_date: signup_date
79
+ )
80
+ grain (customer_id)
81
+ query '''
82
+ select 1 as id, cast('2023-01-01' as date) as signup_date
83
+ union all
84
+ select 2 as id, cast('2023-01-02' as date) as signup_date
85
+ ''';
86
+
87
+ """
88
+ )
89
+ assert env.environment.concepts["signup_date"].keys == {"local.customer_id"}
90
+ assert env.environment.concepts["signup_date.year"].keys == {"local.signup_date"}
91
+ results = env.execute_query(
92
+ """SELECT
93
+ signup_date.year::int::year as signup_year;"""
94
+ )
95
+
96
+ for row in results.fetchall():
97
+ assert row.signup_year == 2023
98
+
99
+ assert "year" in env.environment.data_types
100
+
101
+ assert env.environment.concepts["signup_year"].datatype.traits == ["year"]
102
+
103
+ results = env.execute_query(
104
+ """SELECT
105
+ signup_date.year::int::dtypes.year as signup_year_two;"""
106
+ )
107
+
108
+ for row in results.fetchall():
109
+ assert row.signup_year_two == 2023
110
+
111
+ assert "dtypes.year" in env.environment.data_types
112
+
113
+ assert env.environment.concepts["signup_year_two"].datatype.traits == [
114
+ "dtypes.year"
115
+ ]
116
+
117
+
47
118
  def test_typing_aggregate():
48
119
  env = Dialects.DUCK_DB.default_executor()
49
120
  env.environment.parse(
@@ -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.23"
7
+ __version__ = "0.0.3.25"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -54,7 +54,7 @@ def generate_date_concepts(concept: Concept, environment: Environment):
54
54
  grain=concept.grain,
55
55
  namespace=concept.namespace,
56
56
  keys=set(
57
- concept.address,
57
+ [concept.address],
58
58
  ),
59
59
  metadata=Metadata(
60
60
  description=f"Auto-derived from {base_description}. {FUNCTION_DESCRIPTION_MAPS.get(ftype, ftype.value)}. ",
@@ -102,7 +102,7 @@ def generate_datetime_concepts(concept: Concept, environment: Environment):
102
102
  grain=concept.grain,
103
103
  namespace=concept.namespace,
104
104
  keys=set(
105
- concept.address,
105
+ [concept.address],
106
106
  ),
107
107
  metadata=Metadata(
108
108
  description=f"Auto-derived from {base_description}. {FUNCTION_DESCRIPTION_MAPS.get(ftype, ftype.value)}.",
@@ -717,7 +717,6 @@ class FunctionFactory:
717
717
  output_purpose = Purpose.METRIC
718
718
  else:
719
719
  output_purpose = Purpose.PROPERTY
720
-
721
720
  return Function(
722
721
  operator=operator,
723
722
  arguments=full_args,
@@ -255,7 +255,7 @@ class Parenthetical(
255
255
  return arg_to_datatype(self.content)
256
256
 
257
257
 
258
- class Conditional(Mergeable, ConceptArgs, Namespaced, BaseModel):
258
+ class Conditional(Mergeable, ConceptArgs, Namespaced, DataTyped, BaseModel):
259
259
  left: Expr
260
260
  right: Expr
261
261
  operator: BooleanOperator
@@ -357,6 +357,11 @@ class Conditional(Mergeable, ConceptArgs, Namespaced, BaseModel):
357
357
  output += self.right.existence_arguments
358
358
  return output
359
359
 
360
+ @property
361
+ def output_datatype(self):
362
+ # a conditional is always a boolean
363
+ return DataType.BOOL
364
+
360
365
  def decompose(self):
361
366
  chunks = []
362
367
  if self.operator == BooleanOperator.AND:
@@ -545,7 +550,7 @@ class Grain(Namespaced, BaseModel):
545
550
  return self.__add__(other)
546
551
 
547
552
 
548
- class Comparison(ConceptArgs, Mergeable, Namespaced, BaseModel):
553
+ class Comparison(ConceptArgs, Mergeable, DataTyped, Namespaced, BaseModel):
549
554
  left: Union[
550
555
  int,
551
556
  str,
@@ -745,6 +750,11 @@ class Comparison(ConceptArgs, Mergeable, Namespaced, BaseModel):
745
750
  output += self.right.existence_arguments
746
751
  return output
747
752
 
753
+ @property
754
+ def output_datatype(self):
755
+ # a conditional is always a boolean
756
+ return DataType.BOOL
757
+
748
758
 
749
759
  class SubselectComparison(Comparison):
750
760
  def __eq__(self, other):
@@ -1035,6 +1045,7 @@ class Concept(Addressable, DataTyped, ConceptArgs, Mergeable, Namespaced, BaseMo
1035
1045
  pkeys.update(parent_keys)
1036
1046
  raw_keys = pkeys
1037
1047
  # deduplicate
1048
+
1038
1049
  final_grain = Grain.from_concepts(raw_keys, environment)
1039
1050
  keys = final_grain.components
1040
1051
  return new_lineage, final_grain, keys
@@ -1400,7 +1411,7 @@ def get_basic_type(
1400
1411
  if isinstance(type, NumericType):
1401
1412
  return DataType.NUMERIC
1402
1413
  if isinstance(type, TraitDataType):
1403
- return type.type
1414
+ return get_basic_type(type.type)
1404
1415
  return type
1405
1416
 
1406
1417
 
@@ -2244,6 +2255,11 @@ class CustomType(BaseModel):
2244
2255
  name: str
2245
2256
  type: DataType
2246
2257
 
2258
+ def with_namespace(self, namespace: str) -> "CustomType":
2259
+ return CustomType.model_construct(
2260
+ name=address_with_namespace(self.name, namespace), type=self.type
2261
+ )
2262
+
2247
2263
 
2248
2264
  Expr = (
2249
2265
  MagicConstants
@@ -2284,6 +2300,7 @@ FuncArgs = (
2284
2300
  | date
2285
2301
  | datetime
2286
2302
  | MapWrapper[Any, Any]
2303
+ | TraitDataType
2287
2304
  | DataType
2288
2305
  | ListType
2289
2306
  | MapType
@@ -77,6 +77,7 @@ from trilogy.core.models.core import (
77
77
  MapWrapper,
78
78
  NumericType,
79
79
  StructType,
80
+ TraitDataType,
80
81
  TupleWrapper,
81
82
  arg_to_datatype,
82
83
  )
@@ -1066,7 +1067,9 @@ class BuildFunction(DataTyped, BuildConceptArgs, BaseModel):
1066
1067
  # class BuildFunction(Function):
1067
1068
  operator: FunctionType
1068
1069
  arg_count: int = Field(default=1)
1069
- output_datatype: DataType | ListType | StructType | MapType | NumericType
1070
+ output_datatype: (
1071
+ DataType | ListType | StructType | MapType | NumericType | TraitDataType
1072
+ )
1070
1073
  output_purpose: Purpose
1071
1074
  valid_inputs: Optional[
1072
1075
  Union[
@@ -1082,6 +1085,7 @@ class BuildFunction(DataTyped, BuildConceptArgs, BaseModel):
1082
1085
  date,
1083
1086
  datetime,
1084
1087
  MapWrapper[Any, Any],
1088
+ TraitDataType,
1085
1089
  DataType,
1086
1090
  ListType,
1087
1091
  MapType,
@@ -1873,6 +1877,10 @@ class Factory:
1873
1877
  new.gen_concept_list_caches()
1874
1878
  return new
1875
1879
 
1880
+ @build.register
1881
+ def _(self, base: TraitDataType):
1882
+ return base
1883
+
1876
1884
  @build.register
1877
1885
  def _(self, base: Datasource):
1878
1886
  local_cache: dict[str, BuildConcept] = {}
@@ -105,12 +105,15 @@ class DataType(Enum):
105
105
 
106
106
 
107
107
  class TraitDataType(BaseModel):
108
- type: DataType
108
+ type: DataType | NumericType | StructType | ListType | MapType
109
109
  traits: list[str]
110
110
 
111
111
  def __hash__(self):
112
112
  return hash(self.type)
113
113
 
114
+ def __str__(self) -> str:
115
+ return f"Trait<{self.type}, {self.traits}>"
116
+
114
117
  def __eq__(self, other):
115
118
  if isinstance(other, DataType):
116
119
  return self.type == other
@@ -131,6 +134,9 @@ class NumericType(BaseModel):
131
134
  precision: int = 20
132
135
  scale: int = 5
133
136
 
137
+ def __str__(self) -> str:
138
+ return f"Numeric({self.precision},{self.scale})"
139
+
134
140
  @property
135
141
  def data_type(self):
136
142
  return DataType.NUMERIC
@@ -353,12 +359,21 @@ def merge_datatypes(
353
359
 
354
360
  def is_compatible_datatype(left, right):
355
361
  # for unknown types, we can't make any assumptions
362
+ if all(
363
+ isinstance(x, NumericType)
364
+ or x in (DataType.INTEGER, DataType.FLOAT, DataType.NUMERIC)
365
+ for x in (left, right)
366
+ ):
367
+ return True
368
+ elif isinstance(left, NumericType) or isinstance(right, NumericType):
369
+ return False
356
370
  if right == DataType.UNKNOWN or left == DataType.UNKNOWN:
357
371
  return True
358
372
  if left == right:
359
373
  return True
360
374
  if {left, right} == {DataType.NUMERIC, DataType.FLOAT}:
361
375
  return True
376
+
362
377
  if {left, right} == {DataType.NUMERIC, DataType.INTEGER}:
363
378
  return True
364
379
  if {left, right} == {DataType.FLOAT, DataType.INTEGER}:
@@ -382,6 +397,8 @@ def arg_to_datatype(arg) -> CONCRETE_TYPES:
382
397
  return DataType.FLOAT
383
398
  elif isinstance(arg, NumericType):
384
399
  return arg
400
+ elif isinstance(arg, TraitDataType):
401
+ return arg
385
402
  elif isinstance(arg, ListWrapper):
386
403
  return ListType(type=arg.type)
387
404
  elif isinstance(arg, DataTyped):
@@ -442,6 +442,13 @@ class Environment(BaseModel):
442
442
  self.functions[address_with_namespace(key, alias)] = (
443
443
  function.with_namespace(alias)
444
444
  )
445
+ for key, type in source.data_types.items():
446
+ if same_namespace:
447
+ self.data_types[key] = type
448
+ else:
449
+ self.data_types[address_with_namespace(key, alias)] = (
450
+ type.with_namespace(alias)
451
+ )
445
452
  return self
446
453
 
447
454
  def add_file_import(
@@ -38,6 +38,7 @@ from trilogy.core.models.core import (
38
38
  MapType,
39
39
  MapWrapper,
40
40
  NumericType,
41
+ TraitDataType,
41
42
  TupleWrapper,
42
43
  )
43
44
  from trilogy.core.models.execute import (
@@ -441,6 +442,7 @@ def is_scalar_condition(
441
442
  | BuildCaseWhen
442
443
  | BuildCaseElse
443
444
  | MagicConstants
445
+ | TraitDataType
444
446
  | DataType
445
447
  | MapWrapper[Any, Any]
446
448
  | ListType
@@ -36,6 +36,7 @@ from trilogy.core.models.core import (
36
36
  MapWrapper,
37
37
  NumericType,
38
38
  StructType,
39
+ TraitDataType,
39
40
  TupleWrapper,
40
41
  )
41
42
  from trilogy.core.models.datasource import Datasource, RawColumnExpr
@@ -510,6 +511,7 @@ class BaseDialect:
510
511
  date,
511
512
  datetime,
512
513
  DataType,
514
+ TraitDataType,
513
515
  MagicConstants,
514
516
  MapWrapper[Any, Any],
515
517
  MapType,
@@ -677,6 +679,8 @@ class BaseDialect:
677
679
  return self.FUNCTION_MAP[FunctionType.DATE_LITERAL](e)
678
680
  elif isinstance(e, datetime):
679
681
  return self.FUNCTION_MAP[FunctionType.DATETIME_LITERAL](e)
682
+ elif isinstance(e, TraitDataType):
683
+ return self.render_expr(e.type, cte=cte, cte_map=cte_map)
680
684
  else:
681
685
  raise ValueError(f"Unable to render type {type(e)} {e}")
682
686
 
@@ -91,6 +91,7 @@ from trilogy.core.models.core import (
91
91
  TupleWrapper,
92
92
  arg_to_datatype,
93
93
  dict_to_map_wrapper,
94
+ is_compatible_datatype,
94
95
  list_to_wrapper,
95
96
  tuple_to_wrapper,
96
97
  )
@@ -146,6 +147,8 @@ SELF_LABEL = "root"
146
147
 
147
148
  MAX_PARSE_DEPTH = 10
148
149
 
150
+ STDLIB_ROOT = Path(__file__).parent.parent
151
+
149
152
 
150
153
  @dataclass
151
154
  class WholeGrainWrapper:
@@ -443,22 +446,36 @@ class ParseToObjects(Transformer):
443
446
  def map_type(self, args) -> MapType:
444
447
  return MapType(key_type=args[0], value_type=args[1])
445
448
 
449
+ @v_args(meta=True)
446
450
  def data_type(
447
- self, args
451
+ self, meta: Meta, args
448
452
  ) -> DataType | TraitDataType | ListType | StructType | MapType | NumericType:
449
453
  resolved = args[0]
450
454
  traits = args[2:]
455
+ base: DataType | TraitDataType | ListType | StructType | MapType | NumericType
451
456
  if isinstance(resolved, StructType):
452
- return resolved
457
+ base = resolved
453
458
  elif isinstance(resolved, ListType):
454
- return resolved
459
+ base = resolved
455
460
  elif isinstance(resolved, NumericType):
456
- return resolved
461
+ base = resolved
457
462
  elif isinstance(resolved, MapType):
458
- return resolved
459
- base = DataType(args[0].lower())
463
+ base = resolved
464
+ else:
465
+ base = DataType(args[0].lower())
460
466
  if traits:
467
+ for trait in traits:
468
+ if trait not in self.environment.data_types:
469
+ raise ParseError(
470
+ f"Invalid trait (type) {trait} for {base}, line {meta.line}."
471
+ )
472
+ matched = self.environment.data_types[trait]
473
+ if not is_compatible_datatype(matched.type, base):
474
+ raise ParseError(
475
+ f"Invalid trait (type) {trait} for {base}, line {meta.line}. Trait expects type {matched.type}, has {base}"
476
+ )
461
477
  return TraitDataType(type=base, traits=traits)
478
+
462
479
  return base
463
480
 
464
481
  def array_comparison(self, args) -> ComparisonOperator:
@@ -929,9 +946,12 @@ class ParseToObjects(Transformer):
929
946
  select=args[-1],
930
947
  )
931
948
 
932
- def resolve_import_address(self, address) -> str:
933
- if isinstance(
934
- self.environment.config.import_resolver, FileSystemImportResolver
949
+ def resolve_import_address(self, address, is_stdlib: bool = False) -> str:
950
+ if (
951
+ isinstance(
952
+ self.environment.config.import_resolver, FileSystemImportResolver
953
+ )
954
+ or is_stdlib
935
955
  ):
936
956
  with open(address, "r", encoding="utf-8") as f:
937
957
  text = f.read()
@@ -939,7 +959,7 @@ class ParseToObjects(Transformer):
939
959
  lookup = address
940
960
  if lookup not in self.environment.config.import_resolver.content:
941
961
  raise ImportError(
942
- f"Unable to import file {lookup}, not found in import resolver"
962
+ f"Unable to import file {lookup}, not found in imsport resolver"
943
963
  )
944
964
  text = self.environment.config.import_resolver.content[lookup]
945
965
  else:
@@ -957,13 +977,17 @@ class ParseToObjects(Transformer):
957
977
  cache_key = args[0]
958
978
  input_path = args[0]
959
979
  path = input_path.split(".")
960
-
961
- if isinstance(
980
+ is_stdlib = False
981
+ if path[0] == "std":
982
+ is_stdlib = True
983
+ target = join(STDLIB_ROOT, *path) + ".preql"
984
+ token_lookup: Path | str = Path(target)
985
+ elif isinstance(
962
986
  self.environment.config.import_resolver, FileSystemImportResolver
963
987
  ):
964
988
  target = join(self.environment.working_path, *path) + ".preql"
965
989
  # tokens + text are cached by path
966
- token_lookup: Path | str = Path(target)
990
+ token_lookup = Path(target)
967
991
  elif isinstance(self.environment.config.import_resolver, DictImportResolver):
968
992
  target = ".".join(path)
969
993
  token_lookup = target
@@ -984,7 +1008,7 @@ class ParseToObjects(Transformer):
984
1008
  raw_tokens = self.tokens[token_lookup]
985
1009
  text = self.text_lookup[token_lookup]
986
1010
  else:
987
- text = self.resolve_import_address(target)
1011
+ text = self.resolve_import_address(target, is_stdlib)
988
1012
  self.text_lookup[token_lookup] = text
989
1013
 
990
1014
  try:
@@ -1238,8 +1262,9 @@ class ParseToObjects(Transformer):
1238
1262
  def type_declaration(self, meta: Meta, args) -> TypeDeclaration:
1239
1263
  key = args[0]
1240
1264
  datatype = args[1]
1241
- self.environment.data_types[key] = datatype
1242
- return TypeDeclaration(type=CustomType(name=key, type=datatype))
1265
+ new = CustomType(name=key, type=datatype)
1266
+ self.environment.data_types[key] = new
1267
+ return TypeDeclaration(type=new)
1243
1268
 
1244
1269
  def int_lit(self, args):
1245
1270
  return int("".join(args))
File without changes
File without changes
File without changes
File without changes