pytrilogy 0.0.3.95__py3-none-any.whl → 0.0.3.97__py3-none-any.whl

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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.95
3
+ Version: 0.0.3.97
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Home-page:
6
6
  Author:
@@ -136,11 +136,11 @@ Versus SQL, Trilogy aims to:
136
136
 
137
137
  | Backend | Status | Notes |
138
138
  |---------|--------|-------|
139
- | **BigQuery** | Core | Full support |
140
- | **DuckDB** | Core | Full support |
141
- | **Snowflake** | Core | Full support |
142
- | **SQL Server** | ⚠️ Experimental | Limited testing |
143
- | **Presto** | ⚠️ Experimental | Limited testing |
139
+ | **BigQuery** | Core | Full support |
140
+ | **DuckDB** | Core | Full support |
141
+ | **Snowflake** | Core | Full support |
142
+ | **SQL Server** | Experimental | Limited testing |
143
+ | **Presto** | Experimental | Limited testing |
144
144
 
145
145
  ## Examples
146
146
 
@@ -311,7 +311,44 @@ trilogy fmt <path to trilogy file>
311
311
  - [Public model repository](https://github.com/trilogydata/trilogy-public-models) - Great place for modeling examples
312
312
  - [Full documentation](https://trilogydata.dev/)
313
313
 
314
- ## Syntax Reference
314
+ ## Python API Integration
315
+
316
+ ### Root Imports
317
+
318
+ Are stable and should be sufficient for executing code from Trilogy as text.
319
+
320
+ ```python
321
+ from pytrilogy import Executor, Dialect
322
+ ```
323
+
324
+ ### Authoring Imports
325
+
326
+ Are also stable, and should be used for cases which programatically generate Trilogy statements without a base text format
327
+ or need to process/transform parsed code in more complicated ways.
328
+
329
+ ```python
330
+ from pytrilogy.authoring import Concept, Function, ...
331
+ ```
332
+
333
+ ### Other Imports
334
+
335
+ Are likely to be unstable. Open an issue if you need to take dependencies on other modules outside those two paths.
336
+
337
+ ## MCP/Server
338
+
339
+ Trilogy is straightforward to run as a server/MCP server; the former to generate SQL on demand and integrate into other tools, and MCP
340
+ for full interactive query loops.
341
+
342
+ This makes it easy to integrate Trilogy into existing tools or workflows.
343
+
344
+ You can see examples of both use cases in the trilogy-studio codebase [here](https://github.com/trilogy-data/trilogy-studio-core)
345
+ and install and run an MCP server directly with that codebase.
346
+
347
+ If you're interested in a more fleshed out standalone server or MCP server, please open an issue and we'll prioritize it!
348
+
349
+ ## Trilogy Syntax Reference
350
+
351
+ Not exhaustive - see [documentation](https://trilogydata.dev/) for more details.
315
352
 
316
353
  ### Import
317
354
  ```sql
@@ -1,23 +1,22 @@
1
- pytrilogy-0.0.3.95.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
2
- trilogy/__init__.py,sha256=2SHgfndSm3yWw7j2feLaNCtzQoSJCcpfq9h1nEy7XF8,303
3
- trilogy/compiler.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- trilogy/constants.py,sha256=eKb_EJvSqjN9tGbdVEViwdtwwh8fZ3-jpOEDqL71y70,1691
1
+ pytrilogy-0.0.3.97.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
2
+ trilogy/__init__.py,sha256=cYPR5Qx9Z_vJCc8nN83bLxuX2AWSJ8GNmHHEIFoyexA,303
3
+ trilogy/constants.py,sha256=SSsRMg9HTou259nMKAw-rJNBgzkWjQ3QIQXcrq9i5Kk,1717
5
4
  trilogy/engine.py,sha256=3MiADf5MKcmxqiHBuRqiYdsXiLj7oitDfVvXvHrfjkA,2178
6
- trilogy/executor.py,sha256=AwzC9J2GFfipc4PupFa0mpx5GXghsr0v2djpDV0D70M,19559
5
+ trilogy/executor.py,sha256=YfSjuJ0FVm2gHnNgmUlXijWDTUFjqq9FNakWpeEYO48,15769
7
6
  trilogy/parser.py,sha256=o4cfk3j3yhUFoiDKq9ZX_GjBF3dKhDjXEwb63rcBkBM,293
8
7
  trilogy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
8
  trilogy/render.py,sha256=qQWwduymauOlB517UtM-VGbVe8Cswa4UJub5aGbSO6c,1512
10
9
  trilogy/utility.py,sha256=euQccZLKoYBz0LNg5tzLlvv2YHvXh9HArnYp1V3uXsM,763
11
- trilogy/authoring/__init__.py,sha256=3VQyDVexk-Lpk5oWFA26pvNkJELDiLbVbet5tv7Cmdg,2671
10
+ trilogy/authoring/__init__.py,sha256=TABMOETSMERrWuyDLR0nK4ISlqR0yaqeXrmuOdrSvAY,3060
12
11
  trilogy/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
12
  trilogy/core/constants.py,sha256=nizWYDCJQ1bigQMtkNIEMNTcN0NoEAXiIHLzpelxQ24,201
14
- trilogy/core/enums.py,sha256=EusAzz7o_YrWf64TLIED7MfziFOJk8EHM8se5W3nyJk,8644
13
+ trilogy/core/enums.py,sha256=H8I2Dz4POHZ4ixYCGzNs4c3KDqxLQklGLVfmje1DSMo,8877
15
14
  trilogy/core/env_processor.py,sha256=H-rr2ALj31l5oh3FqeI47Qju6OOfiXBacXNJGNZ92zQ,4521
16
15
  trilogy/core/environment_helpers.py,sha256=TRlqVctqIRBxzfjRBmpQsAVoiCcsEKBhG1B6PUE0l1M,12743
17
16
  trilogy/core/ergonomics.py,sha256=e-7gE29vPLFdg0_A1smQ7eOrUwKl5VYdxRSTddHweRA,1631
18
- trilogy/core/exceptions.py,sha256=0Lmc3awJYx94k6uifbHc-EIqlFGV6YrX0QIwP84D4a4,1150
17
+ trilogy/core/exceptions.py,sha256=fI16oTNCVMMAJFSn2AFzZVapzsF5M9WbdN5e5UixwXc,2807
19
18
  trilogy/core/functions.py,sha256=ESUWMRmwtavwCLl6z1NP9EFzWTJoXn3orTaaOSsj33Q,33093
20
- trilogy/core/graph_models.py,sha256=jfYjDQoMtTkSM3n16BCYfhmvAwMHMU-nuQamlYkwzdM,3356
19
+ trilogy/core/graph_models.py,sha256=4EWFTHGfYd72zvS2HYoV6hm7nMC_VEd7vWr6txY-ig0,3400
21
20
  trilogy/core/internal.py,sha256=r9QagDB2GvpqlyD_I7VrsfbVfIk5mnok2znEbv72Aa4,2681
22
21
  trilogy/core/optimization.py,sha256=ojpn-p79lr03SSVQbbw74iPCyoYpDYBmj1dbZ3oXCjI,8860
23
22
  trilogy/core/query_processor.py,sha256=uqygDJqkjIH4vLP-lbGRgTN7rRcYEkr3KGqNimNw_80,20345
@@ -75,20 +74,22 @@ trilogy/core/statements/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
75
74
  trilogy/core/statements/author.py,sha256=VFzylve72fw0tqMSP5Yiwp8--_r92b9zzX1XAdxuTYQ,15963
76
75
  trilogy/core/statements/build.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
77
76
  trilogy/core/statements/common.py,sha256=VnVLULQg1TJLNUFzJaROT1tsf2ewk3IpuhvZaP36R6A,535
78
- trilogy/core/statements/execute.py,sha256=2ev8nnf41MAgJJAb5gStd__d9hZNOR2pSUTYGFdHvhU,2342
77
+ trilogy/core/statements/execute.py,sha256=kiwJcVeMa4wZR-xLfM2oYOJ9DeyJkP8An38WFyJxktM,2413
79
78
  trilogy/core/validation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
80
- trilogy/core/validation/common.py,sha256=xMx5jF3HDydaGwp9ybBo_NVWOqsmo_mimofdhAv8Kjs,3139
81
- trilogy/core/validation/concept.py,sha256=HLY8zzMP15VOotvT3lVLh7h_lLJ9evZJ3qaG9huwG50,4344
82
- trilogy/core/validation/datasource.py,sha256=92l0T0kxzBnGFT1zW_LvZHMkvkfoWvqQf0Nk4qysWts,6346
83
- trilogy/core/validation/environment.py,sha256=8TuLfIE49zSEtvHPBlNC8QJ5fnS0QZqM0TFpHhy2GN0,2790
79
+ trilogy/core/validation/common.py,sha256=Sd-towAX1uSDe3dK51FcVtIwVrMhayEwdHqhzeJHro0,4776
80
+ trilogy/core/validation/concept.py,sha256=Jr8H9wn0-OMJeSFD5GydiB-LvCSj4CUe72evHjRq85E,4991
81
+ trilogy/core/validation/datasource.py,sha256=d9AQNcukIRgN2spItPsXFiNtlZva-lDnfei3i06yQCE,6489
82
+ trilogy/core/validation/environment.py,sha256=waBPMB6rxa-9SKUAdhfT8UUDqh4a76n27pKtRDre560,2834
83
+ trilogy/core/validation/fix.py,sha256=Z818UFNLxndMTLiyhB3doLxIfnOZ-16QGvVFWuD7UsA,3750
84
84
  trilogy/dialect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
85
- trilogy/dialect/base.py,sha256=QwjQm2os5prVgCaWgpyk1bIL0okCMOGFQdAnKcYXmIo,49392
85
+ trilogy/dialect/base.py,sha256=0QVHv4F0t3_gRQrZ0woFoUNKu7vaXGo-BG1l47CZUKc,49698
86
86
  trilogy/dialect/bigquery.py,sha256=XS3hpybeowgfrOrkycAigAF3NX2YUzTzfgE6f__2fT4,4316
87
87
  trilogy/dialect/common.py,sha256=tSthIZOXXRPQ4KeMKnDDsH7KlTmf2EVqigVtLyoc4zc,6071
88
88
  trilogy/dialect/config.py,sha256=olnyeVU5W5T6b9-dMeNAnvxuPlyc2uefb7FRME094Ec,3834
89
89
  trilogy/dialect/dataframe.py,sha256=RUbNgReEa9g3pL6H7fP9lPTrAij5pkqedpZ99D8_5AE,1522
90
90
  trilogy/dialect/duckdb.py,sha256=JoUvQ19WvgxoaJkGLM7DPXOd1H0394k3vBiblksQzOI,5631
91
91
  trilogy/dialect/enums.py,sha256=FRNYQ5-w-B6-X0yXKNU5g9GowsMlERFogTC5u2nxL_s,4740
92
+ trilogy/dialect/metadata.py,sha256=Vt4-p82bD1ijqeoI2dagUVUbC-KgNNJ2MvDwQIa5mG8,7034
92
93
  trilogy/dialect/postgres.py,sha256=el2PKwfyvWGk5EZtLudqAH5ewLitY1sFHJiocBSyxyM,3393
93
94
  trilogy/dialect/presto.py,sha256=k1IaeilR3nzPC9Hp7jlAdzJ7TsuxB3LQTBQ28MYE7O8,3715
94
95
  trilogy/dialect/snowflake.py,sha256=T6_mKfhpDazB1xQxqFLS2AJwzwzBcPYY6_qxRnAtFBs,3326
@@ -103,9 +104,9 @@ trilogy/parsing/common.py,sha256=550-L0444GUuBFdiDWkOg_DxnMXtcJFUMES2R5zlwik,310
103
104
  trilogy/parsing/config.py,sha256=Z-DaefdKhPDmSXLgg5V4pebhSB0h590vI0_VtHnlukI,111
104
105
  trilogy/parsing/exceptions.py,sha256=Xwwsv2C9kSNv2q-HrrKC1f60JNHShXcCMzstTSEbiCw,154
105
106
  trilogy/parsing/helpers.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
106
- trilogy/parsing/parse_engine.py,sha256=fZGFNN7PEVJ7_tk0GxBHJQerIJTifbrjoMcHfTR_TYk,81647
107
- trilogy/parsing/render.py,sha256=HSNntD82GiiwHT-TWPLXAaIMWLYVV5B5zQEsgwrHIBE,19605
108
- trilogy/parsing/trilogy.lark,sha256=9-SMrKFGZLdBTNheK1szif0VYOIt5m0xhVd8pOFCByU,16267
107
+ trilogy/parsing/parse_engine.py,sha256=Zd4Zpj8k_Q95onXlThV_AWSwjUAGDsD57uTyKKmXxNI,81784
108
+ trilogy/parsing/render.py,sha256=OirN76I8z9xDNGvydO4DhsTsN-NS_1SC5_77_V23CkI,20515
109
+ trilogy/parsing/trilogy.lark,sha256=rM4WleeyGhoRgU-FOGcaeHOzZcYVxN4f13e_3B4OeLQ,16389
109
110
  trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
110
111
  trilogy/scripts/trilogy.py,sha256=1L0XrH4mVHRt1C9T1HnaDv2_kYEfbWTb5_-cBBke79w,3774
111
112
  trilogy/std/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -116,8 +117,8 @@ trilogy/std/money.preql,sha256=XWwvAV3WxBsHX9zfptoYRnBigcfYwrYtBHXTME0xJuQ,2082
116
117
  trilogy/std/net.preql,sha256=WZCuvH87_rZntZiuGJMmBDMVKkdhTtxeHOkrXNwJ1EE,416
117
118
  trilogy/std/ranking.preql,sha256=LDoZrYyz4g3xsII9XwXfmstZD-_92i1Eox1UqkBIfi8,83
118
119
  trilogy/std/report.preql,sha256=LbV-XlHdfw0jgnQ8pV7acG95xrd1-p65fVpiIc-S7W4,202
119
- pytrilogy-0.0.3.95.dist-info/METADATA,sha256=00qsW428K_K33ZhoMDKEMPViV1eCY6Gm6MJZ71fRHRo,10441
120
- pytrilogy-0.0.3.95.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
121
- pytrilogy-0.0.3.95.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
122
- pytrilogy-0.0.3.95.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
123
- pytrilogy-0.0.3.95.dist-info/RECORD,,
120
+ pytrilogy-0.0.3.97.dist-info/METADATA,sha256=t-pwxx9XVd6oSuAMdPfTDHFaY7AevCESieu_mK6YrcU,11683
121
+ pytrilogy-0.0.3.97.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
122
+ pytrilogy-0.0.3.97.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
123
+ pytrilogy-0.0.3.97.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
124
+ pytrilogy-0.0.3.97.dist-info/RECORD,,
trilogy/__init__.py CHANGED
@@ -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.95"
7
+ __version__ = "0.0.3.97"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -60,68 +60,82 @@ from trilogy.core.statements.author import (
60
60
  RowsetDerivationStatement,
61
61
  SelectItem,
62
62
  SelectStatement,
63
+ ShowCategory,
64
+ ShowStatement,
65
+ ValidateStatement,
63
66
  )
64
67
  from trilogy.parsing.common import arbitrary_to_concept, arg_to_datatype
65
68
 
66
69
  __all__ = [
67
- "Concept",
68
- "Function",
69
- "WhereClause",
70
- "Comparison",
71
- "FilterItem",
72
- "CaseWhen",
73
- "CaseElse",
74
- "AggregateWrapper",
75
- "WindowItem",
76
- "WindowOrder",
77
- "WindowType",
78
- "WindowItemOrder",
79
- "WindowItemOver",
80
- "DataType",
81
- "StructType",
82
- "ArrayType",
83
- "NumericType",
84
- "Grain",
85
- "RowsetDerivationStatement",
86
- "MapType",
87
- "ListWrapper",
70
+ # trilogy.constants
71
+ "DEFAULT_NAMESPACE",
72
+ # trilogy.core.enums
73
+ "BooleanOperator",
74
+ "ComparisonOperator",
75
+ "FunctionClass",
88
76
  "FunctionType",
77
+ "InfiniteFunctionArgs",
78
+ "Ordering",
79
+ "Purpose",
80
+ # trilogy.core.functions
89
81
  "FunctionFactory",
90
- "ConceptDeclarationStatement",
91
- "ConceptTransform",
92
- "SelectItem",
93
- "SelectStatement",
94
- "Environment",
82
+ # trilogy.core.models.author
83
+ "AggregateWrapper",
84
+ "CaseElse",
85
+ "CaseWhen",
86
+ "Comparison",
87
+ "Concept",
95
88
  "ConceptRef",
89
+ "Conditional",
90
+ "FilterItem",
91
+ "Function",
92
+ "FunctionCallWrapper",
96
93
  "HavingClause",
97
94
  "MagicConstants",
98
95
  "Metadata",
96
+ "MultiSelectLineage",
99
97
  "OrderBy",
100
98
  "OrderItem",
101
99
  "Parenthetical",
100
+ "RowsetItem",
102
101
  "SubselectComparison",
103
- "Conditional",
104
- "BooleanOperator",
105
- "ComparisonOperator",
106
- "FunctionClass",
107
- "FunctionType",
108
- "InfiniteFunctionArgs",
109
- "Ordering",
110
- "Purpose",
111
- "DEFAULT_NAMESPACE",
112
- "arbitrary_to_concept",
113
- "arg_to_datatype",
114
- "MultiSelectStatement",
115
- "PersistStatement",
116
- "RawSQLStatement",
102
+ "WhereClause",
103
+ "WindowItem",
104
+ "WindowItemOrder",
105
+ "WindowItemOver",
106
+ "WindowOrder",
107
+ "WindowType",
108
+ # trilogy.core.models.core
109
+ "ArrayType",
110
+ "DataType",
111
+ "ListWrapper",
112
+ "MapType",
113
+ "NumericType",
114
+ "StructType",
115
+ "TraitDataType",
116
+ # trilogy.core.models.datasource
117
+ "Address",
117
118
  "Datasource",
118
119
  "DatasourceMetadata",
119
- "MultiSelectLineage",
120
- "RowsetItem",
121
- "FunctionCallWrapper",
120
+ # trilogy.core.models.environment
121
+ "Environment",
122
+ # trilogy.core.statements.author
123
+ "ConceptDeclarationStatement",
124
+ "ConceptTransform",
122
125
  "CopyStatement",
126
+ "Grain",
123
127
  "HasUUID",
124
128
  "ImportStatement",
125
- "Address",
126
- "TraitDataType",
129
+ "MultiSelectStatement",
130
+ "PersistStatement",
131
+ "RawSQLStatement",
132
+ "RowsetDerivationStatement",
133
+ "SelectItem",
134
+ "SelectStatement",
135
+ "ShowCategory",
136
+ "ShowStatement",
137
+ "ValidateStatement",
138
+ # trilogy.parsing.common
139
+ "arbitrary_to_concept",
140
+ "arg_to_datatype",
127
141
  ]
trilogy/constants.py CHANGED
@@ -16,6 +16,7 @@ ENV_CACHE_NAME = ".preql_cache.json"
16
16
 
17
17
  class MagicConstants(Enum):
18
18
  NULL = "null"
19
+ LINE_SEPARATOR = "\n"
19
20
 
20
21
 
21
22
  NULL_VALUE = MagicConstants.NULL
trilogy/core/enums.py CHANGED
@@ -82,6 +82,15 @@ class Modifier(Enum):
82
82
  return Modifier.NULLABLE
83
83
  return super()._missing_(value=strval.capitalize())
84
84
 
85
+ def __lt__(self, other):
86
+ order = [
87
+ Modifier.HIDDEN,
88
+ Modifier.PARTIAL,
89
+ Modifier.NULLABLE,
90
+ Modifier.OPTIONAL,
91
+ ]
92
+ return order.index(self) < order.index(other)
93
+
85
94
 
86
95
  class JoinType(Enum):
87
96
  INNER = "inner"
@@ -1,4 +1,15 @@
1
- from typing import List, Sequence
1
+ from dataclasses import dataclass
2
+ from typing import Any, List, Sequence
3
+
4
+ from trilogy.core.enums import Modifier
5
+ from trilogy.core.models.core import (
6
+ ArrayType,
7
+ DataType,
8
+ MapType,
9
+ NumericType,
10
+ StructType,
11
+ TraitDataType,
12
+ )
2
13
 
3
14
 
4
15
  class UndefinedConceptException(Exception):
@@ -29,7 +40,7 @@ class ModelValidationError(Exception):
29
40
  self,
30
41
  message,
31
42
  children: Sequence["ModelValidationError"] | None = None,
32
- **kwargs
43
+ **kwargs,
33
44
  ):
34
45
  super().__init__(self, message, **kwargs)
35
46
  self.message = message
@@ -40,6 +51,49 @@ class DatasourceModelValidationError(ModelValidationError):
40
51
  pass
41
52
 
42
53
 
54
+ class DatasourceGrainValidationError(DatasourceModelValidationError):
55
+ pass
56
+
57
+
58
+ @dataclass
59
+ class DatasourceColumnBindingData:
60
+ address: str
61
+ value: Any
62
+ value_type: (
63
+ DataType | ArrayType | StructType | MapType | NumericType | TraitDataType
64
+ )
65
+ value_modifiers: List[Modifier]
66
+ actual_type: (
67
+ DataType | ArrayType | StructType | MapType | NumericType | TraitDataType
68
+ )
69
+ actual_modifiers: List[Modifier]
70
+
71
+ def format_failure(self):
72
+ return f"Concept {self.address} value '{self.value}' with type {self.value_modifiers} does not conform to expected type {str(self.actual_type)} with modifiers {self.actual_modifiers}"
73
+
74
+ def is_modifier_issue(self) -> bool:
75
+ return len(self.value_modifiers) > 0 and any(
76
+ [x not in self.actual_modifiers for x in self.value_modifiers]
77
+ )
78
+
79
+ def is_type_issue(self) -> bool:
80
+ return self.value_type != self.actual_type
81
+
82
+
83
+ class DatasourceColumnBindingError(DatasourceModelValidationError):
84
+ def __init__(
85
+ self,
86
+ address: str,
87
+ errors: list[DatasourceColumnBindingData],
88
+ message: str | None = None,
89
+ ):
90
+ if not message:
91
+ message = f"Datasource {address} failed validation. Found rows that do not conform to types: {[failure.format_failure() for failure in errors]}"
92
+ super().__init__(message)
93
+ self.errors = errors
94
+ self.dataset_address = address
95
+
96
+
43
97
  class ConceptModelValidationError(ModelValidationError):
44
98
  pass
45
99
 
@@ -64,13 +64,13 @@ def datasource_to_node(input: BuildDatasource) -> str:
64
64
 
65
65
 
66
66
  class ReferenceGraph(nx.DiGraph):
67
- def __init__(self, *args, **kwargs):
67
+ def __init__(self, *args, **kwargs) -> None:
68
68
  super().__init__(*args, **kwargs)
69
69
  self.concepts: dict[str, BuildConcept] = {}
70
70
  self.datasources: dict[str, BuildDatasource] = {}
71
71
  self.pseudonyms: set[tuple[str, str]] = set()
72
72
 
73
- def copy(self):
73
+ def copy(self) -> "ReferenceGraph":
74
74
  g = ReferenceGraph()
75
75
  g.concepts = self.concepts.copy()
76
76
  g.datasources = self.datasources.copy()
@@ -83,7 +83,7 @@ class ReferenceGraph(nx.DiGraph):
83
83
  # g.add_edges_from(self.edges(data=True))
84
84
  return g
85
85
 
86
- def remove_node(self, n):
86
+ def remove_node(self, n) -> None:
87
87
  if n in self.concepts:
88
88
  del self.concepts[n]
89
89
  if n in self.datasources:
@@ -98,7 +98,7 @@ class ReferenceGraph(nx.DiGraph):
98
98
  self.datasources[node_name] = attr["datasource"]
99
99
  super().add_node(node_name, **attr)
100
100
 
101
- def add_datasource_node(self, node_name, datasource):
101
+ def add_datasource_node(self, node_name, datasource) -> None:
102
102
  self.datasources[node_name] = datasource
103
103
  super().add_node(node_name, datasource=datasource)
104
104
 
@@ -88,6 +88,8 @@ class ProcessedShowStatement:
88
88
  BuildConcept,
89
89
  BuildDatasource,
90
90
  ProcessedQuery,
91
+ ProcessedQueryPersist,
92
+ ProcessedCopyStatement,
91
93
  ProcessedValidateStatement,
92
94
  ProcessedStaticValueOutput,
93
95
  ]
@@ -2,13 +2,25 @@ from dataclasses import dataclass
2
2
  from enum import Enum
3
3
 
4
4
  from trilogy import Environment
5
- from trilogy.authoring import ConceptRef
5
+ from trilogy.authoring import (
6
+ ConceptRef,
7
+ DataType,
8
+ Ordering,
9
+ Purpose,
10
+ )
11
+ from trilogy.constants import MagicConstants
12
+ from trilogy.core.enums import ComparisonOperator, FunctionType
6
13
  from trilogy.core.exceptions import ModelValidationError
7
14
  from trilogy.core.models.build import (
15
+ BuildCaseElse,
16
+ BuildCaseWhen,
8
17
  BuildComparison,
9
18
  BuildConcept,
10
19
  BuildConditional,
11
20
  BuildDatasource,
21
+ BuildFunction,
22
+ BuildOrderBy,
23
+ BuildOrderItem,
12
24
  )
13
25
  from trilogy.core.models.environment import EnvironmentConceptDict
14
26
  from trilogy.core.models.execute import (
@@ -27,7 +39,8 @@ class ExpectationType(Enum):
27
39
  @dataclass
28
40
  class ValidationTest:
29
41
  check_type: ExpectationType
30
- query: str | None = None
42
+ raw_query: ProcessedQuery | None = None
43
+ generated_query: str | None = None
31
44
  expected: str | None = None
32
45
  result: ModelValidationError | None = None
33
46
  ran: bool = True
@@ -38,6 +51,32 @@ class ValidationType(Enum):
38
51
  CONCEPTS = "concepts"
39
52
 
40
53
 
54
+ def build_order_args(concepts: list[BuildConcept]) -> list[BuildFunction]:
55
+ order_args = []
56
+ for concept in concepts:
57
+ order_args.append(
58
+ BuildFunction(
59
+ operator=FunctionType.CASE,
60
+ arguments=[
61
+ BuildCaseWhen(
62
+ comparison=BuildComparison(
63
+ left=concept,
64
+ operator=ComparisonOperator.IS,
65
+ right=MagicConstants.NULL,
66
+ ),
67
+ expr=1,
68
+ ),
69
+ BuildCaseElse(expr=0),
70
+ ],
71
+ output_data_type=DataType.INTEGER,
72
+ output_purpose=Purpose.PROPERTY,
73
+ arg_count=2,
74
+ )
75
+ )
76
+
77
+ return order_args
78
+
79
+
41
80
  def easy_query(
42
81
  concepts: list[BuildConcept],
43
82
  datasource: BuildDatasource,
@@ -80,7 +119,6 @@ def easy_query(
80
119
  group_to_grain=True,
81
120
  base_alias_override=datasource.safe_identifier,
82
121
  )
83
-
84
122
  filter_cte = CTE(
85
123
  name=f"datasource_{datasource.name}_filter",
86
124
  source=QueryDatasource(
@@ -99,6 +137,20 @@ def easy_query(
99
137
  grain=cte.grain,
100
138
  condition=condition,
101
139
  limit=limit,
140
+ order_by=BuildOrderBy(
141
+ items=[
142
+ BuildOrderItem(
143
+ expr=BuildFunction(
144
+ operator=FunctionType.SUM,
145
+ arguments=build_order_args(concepts),
146
+ output_data_type=DataType.INTEGER,
147
+ output_purpose=Purpose.PROPERTY,
148
+ arg_count=len(concepts),
149
+ ),
150
+ order=Ordering.DESCENDING,
151
+ )
152
+ ]
153
+ ),
102
154
  )
103
155
 
104
156
  return ProcessedQuery(