pytrilogy 0.0.3.76__py3-none-any.whl → 0.0.3.78__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.
- {pytrilogy-0.0.3.76.dist-info → pytrilogy-0.0.3.78.dist-info}/METADATA +10 -7
- {pytrilogy-0.0.3.76.dist-info → pytrilogy-0.0.3.78.dist-info}/RECORD +9 -9
- trilogy/__init__.py +1 -1
- trilogy/parsing/parse_engine.py +96 -15
- trilogy/parsing/trilogy.lark +3 -1
- {pytrilogy-0.0.3.76.dist-info → pytrilogy-0.0.3.78.dist-info}/WHEEL +0 -0
- {pytrilogy-0.0.3.76.dist-info → pytrilogy-0.0.3.78.dist-info}/entry_points.txt +0 -0
- {pytrilogy-0.0.3.76.dist-info → pytrilogy-0.0.3.78.dist-info}/licenses/LICENSE.md +0 -0
- {pytrilogy-0.0.3.76.dist-info → pytrilogy-0.0.3.78.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pytrilogy
|
|
3
|
-
Version: 0.0.3.
|
|
3
|
+
Version: 0.0.3.78
|
|
4
4
|
Summary: Declarative, typed query language that compiles to SQL.
|
|
5
5
|
Home-page:
|
|
6
6
|
Author:
|
|
@@ -44,17 +44,20 @@ The Trilogy language is an experiment in better SQL for analytics - a streamline
|
|
|
44
44
|
|
|
45
45
|
[pytrilogy](https://github.com/trilogy-data/pytrilogy) is the reference implementation, written in Python.
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
- decoupling consumption code from specific physical assets
|
|
49
|
-
- better testability and change management
|
|
50
|
-
- reduced boilerplate and opportunity for OLAP style optimization at scale
|
|
47
|
+
### What Trilogy Gives You
|
|
51
48
|
|
|
52
|
-
|
|
49
|
+
- Speed - write faster, with concise, powerful syntax
|
|
50
|
+
- Efficiency - write less SQL, and reuse what you do
|
|
51
|
+
- Fearless refactoring
|
|
52
|
+
- Testability
|
|
53
|
+
- Easy to use for humans and LLMs
|
|
54
|
+
|
|
55
|
+
Trilogy is epsecially targeted at data consumption, providing a rich metadata layer that makes visualizing Trilogy easy and expressive.
|
|
53
56
|
|
|
54
57
|
> [!TIP]
|
|
55
58
|
> You can try Trilogy in a [open-source studio](https://trilogydata.dev/trilogy-studio-core/). More details on the language can be found on the [documentation](https://trilogydata.dev/).
|
|
56
59
|
|
|
57
|
-
|
|
60
|
+
Start in the studio to explore Trilogy. For deeper work and integration, `pytrilogy` can be run locally to parse and execute trilogy model [.preql] files using the `trilogy` CLI tool, or can be run in python by importing the `trilogy` package.
|
|
58
61
|
|
|
59
62
|
Installation: `pip install pytrilogy`
|
|
60
63
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
pytrilogy-0.0.3.
|
|
2
|
-
trilogy/__init__.py,sha256=
|
|
1
|
+
pytrilogy-0.0.3.78.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
|
|
2
|
+
trilogy/__init__.py,sha256=eSf6G95oP2fg8vK_MLgUDxm7x3z05oIQBWOhr5hO8fg,303
|
|
3
3
|
trilogy/compiler.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
trilogy/constants.py,sha256=eKb_EJvSqjN9tGbdVEViwdtwwh8fZ3-jpOEDqL71y70,1691
|
|
5
5
|
trilogy/engine.py,sha256=OK2RuqCIUId6yZ5hfF8J1nxGP0AJqHRZiafcowmW0xc,1728
|
|
@@ -97,9 +97,9 @@ trilogy/parsing/common.py,sha256=yV1AckK0h8u1OFeGQBTMu-wuW5m63c5CcZuPicsTH_w,306
|
|
|
97
97
|
trilogy/parsing/config.py,sha256=Z-DaefdKhPDmSXLgg5V4pebhSB0h590vI0_VtHnlukI,111
|
|
98
98
|
trilogy/parsing/exceptions.py,sha256=Xwwsv2C9kSNv2q-HrrKC1f60JNHShXcCMzstTSEbiCw,154
|
|
99
99
|
trilogy/parsing/helpers.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
100
|
-
trilogy/parsing/parse_engine.py,sha256=
|
|
100
|
+
trilogy/parsing/parse_engine.py,sha256=zVPHfYYHH8n_zIDR0Esrhj17recy5142akiEItDOtEw,77987
|
|
101
101
|
trilogy/parsing/render.py,sha256=HSNntD82GiiwHT-TWPLXAaIMWLYVV5B5zQEsgwrHIBE,19605
|
|
102
|
-
trilogy/parsing/trilogy.lark,sha256=
|
|
102
|
+
trilogy/parsing/trilogy.lark,sha256=wiGXJdKfPTG7E_XdkN1rf9g9Yy1-UMVAXyTxtrBPm9w,15037
|
|
103
103
|
trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
104
104
|
trilogy/scripts/trilogy.py,sha256=1L0XrH4mVHRt1C9T1HnaDv2_kYEfbWTb5_-cBBke79w,3774
|
|
105
105
|
trilogy/std/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -110,8 +110,8 @@ trilogy/std/money.preql,sha256=XWwvAV3WxBsHX9zfptoYRnBigcfYwrYtBHXTME0xJuQ,2082
|
|
|
110
110
|
trilogy/std/net.preql,sha256=7l7MqIjs6TDCpO6dBAoNJU81Ex255jZRK36kBgE1GDs,158
|
|
111
111
|
trilogy/std/ranking.preql,sha256=LDoZrYyz4g3xsII9XwXfmstZD-_92i1Eox1UqkBIfi8,83
|
|
112
112
|
trilogy/std/report.preql,sha256=LbV-XlHdfw0jgnQ8pV7acG95xrd1-p65fVpiIc-S7W4,202
|
|
113
|
-
pytrilogy-0.0.3.
|
|
114
|
-
pytrilogy-0.0.3.
|
|
115
|
-
pytrilogy-0.0.3.
|
|
116
|
-
pytrilogy-0.0.3.
|
|
117
|
-
pytrilogy-0.0.3.
|
|
113
|
+
pytrilogy-0.0.3.78.dist-info/METADATA,sha256=nWclkhdYd5C_jRGPwLej4lpsxDB7vqDJNeU8P8tU7Es,9589
|
|
114
|
+
pytrilogy-0.0.3.78.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
115
|
+
pytrilogy-0.0.3.78.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
|
|
116
|
+
pytrilogy-0.0.3.78.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
|
|
117
|
+
pytrilogy-0.0.3.78.dist-info/RECORD,,
|
trilogy/__init__.py
CHANGED
trilogy/parsing/parse_engine.py
CHANGED
|
@@ -7,7 +7,7 @@ from pathlib import Path
|
|
|
7
7
|
from re import IGNORECASE
|
|
8
8
|
from typing import Any, List, Optional, Tuple, Union
|
|
9
9
|
|
|
10
|
-
from lark import Lark, ParseTree, Transformer, Tree, v_args
|
|
10
|
+
from lark import Lark, ParseTree, Token, Transformer, Tree, v_args
|
|
11
11
|
from lark.exceptions import (
|
|
12
12
|
UnexpectedCharacters,
|
|
13
13
|
UnexpectedEOF,
|
|
@@ -2091,6 +2091,53 @@ def parse_text_raw(text: str, environment: Optional[Environment] = None):
|
|
|
2091
2091
|
PARSER.parse(text)
|
|
2092
2092
|
|
|
2093
2093
|
|
|
2094
|
+
ERROR_CODES: dict[int, str] = {
|
|
2095
|
+
# 100 code are SQL compatability errors
|
|
2096
|
+
101: "Using FROM keyword? Trilogy does not have a FROM clause (Datasource resolution is automatic).",
|
|
2097
|
+
# 200 codes relate to required explicit syntax (we could loosen these?)
|
|
2098
|
+
201: 'Missing alias? Alias must be specified with "AS" - e.g. `SELECT x+1 AS y`',
|
|
2099
|
+
210: "Missing order direction? Order by must be explicit about direction - specify `asc` or `desc`.",
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2102
|
+
DEFAULT_ERROR_SPAN: int = 30
|
|
2103
|
+
|
|
2104
|
+
|
|
2105
|
+
def inject_context_maker(pos: int, text: str, span: int = 40) -> str:
|
|
2106
|
+
"""Returns a pretty string pinpointing the error in the text,
|
|
2107
|
+
with span amount of context characters around it.
|
|
2108
|
+
|
|
2109
|
+
Note:
|
|
2110
|
+
The parser doesn't hold a copy of the text it has to parse,
|
|
2111
|
+
so you have to provide it again
|
|
2112
|
+
"""
|
|
2113
|
+
|
|
2114
|
+
start = max(pos - span, 0)
|
|
2115
|
+
end = pos + span
|
|
2116
|
+
if not isinstance(text, bytes):
|
|
2117
|
+
|
|
2118
|
+
before = text[start:pos].rsplit("\n", 1)[-1]
|
|
2119
|
+
after = text[pos:end].split("\n", 1)[0]
|
|
2120
|
+
if end > len(text):
|
|
2121
|
+
rcap = ""
|
|
2122
|
+
elif not after[-1].isspace():
|
|
2123
|
+
rcap = "..."
|
|
2124
|
+
if start > 0 and not before[0].isspace():
|
|
2125
|
+
lcap = "..."
|
|
2126
|
+
else:
|
|
2127
|
+
lcap = ""
|
|
2128
|
+
lpad = " "
|
|
2129
|
+
rpad = " "
|
|
2130
|
+
if before.endswith(" "):
|
|
2131
|
+
lpad = ""
|
|
2132
|
+
if after.startswith(" "):
|
|
2133
|
+
rpad = ""
|
|
2134
|
+
return f"{lcap}{before}{lpad}???{rpad}{after}{rcap}"
|
|
2135
|
+
else:
|
|
2136
|
+
before = text[start:pos].rsplit(b"\n", 1)[-1]
|
|
2137
|
+
after = text[pos:end].split(b"\n", 1)[0]
|
|
2138
|
+
return (before + b" ??? " + after).decode("ascii", "backslashreplace")
|
|
2139
|
+
|
|
2140
|
+
|
|
2094
2141
|
def parse_text(
|
|
2095
2142
|
text: str,
|
|
2096
2143
|
environment: Optional[Environment] = None,
|
|
@@ -2108,6 +2155,48 @@ def parse_text(
|
|
|
2108
2155
|
| None
|
|
2109
2156
|
],
|
|
2110
2157
|
]:
|
|
2158
|
+
def _create_syntax_error(code: int, pos: int, text: str) -> InvalidSyntaxException:
|
|
2159
|
+
"""Helper to create standardized syntax error with context."""
|
|
2160
|
+
return InvalidSyntaxException(
|
|
2161
|
+
f"Syntax [{code}]: "
|
|
2162
|
+
+ ERROR_CODES[code]
|
|
2163
|
+
+ "\nLocation:\n"
|
|
2164
|
+
+ inject_context_maker(pos, text.replace("\n", " "), DEFAULT_ERROR_SPAN)
|
|
2165
|
+
)
|
|
2166
|
+
|
|
2167
|
+
def _create_generic_syntax_error(
|
|
2168
|
+
message: str, pos: int, text: str
|
|
2169
|
+
) -> InvalidSyntaxException:
|
|
2170
|
+
"""Helper to create generic syntax error with context."""
|
|
2171
|
+
return InvalidSyntaxException(
|
|
2172
|
+
message
|
|
2173
|
+
+ "\nLocation:\n"
|
|
2174
|
+
+ inject_context_maker(pos, text.replace("\n", " "), DEFAULT_ERROR_SPAN)
|
|
2175
|
+
)
|
|
2176
|
+
|
|
2177
|
+
def _handle_unexpected_token(e: UnexpectedToken, text: str) -> None:
|
|
2178
|
+
"""Handle UnexpectedToken errors with specific logic."""
|
|
2179
|
+
# Handle ordering direction error
|
|
2180
|
+
pos = e.pos_in_stream or 0
|
|
2181
|
+
if e.expected == {"ORDERING_DIRECTION"}:
|
|
2182
|
+
raise _create_syntax_error(210, pos, text)
|
|
2183
|
+
|
|
2184
|
+
# Handle FROM token error
|
|
2185
|
+
parsed_tokens = [x.value for x in e.token_history] if e.token_history else []
|
|
2186
|
+
if parsed_tokens == ["FROM"]:
|
|
2187
|
+
raise _create_syntax_error(101, pos, text)
|
|
2188
|
+
|
|
2189
|
+
# Attempt recovery for aliasing
|
|
2190
|
+
try:
|
|
2191
|
+
e.interactive_parser.feed_token(Token("AS", "AS"))
|
|
2192
|
+
e.interactive_parser.feed_token(Token("IDENTIFIER", e.token.value))
|
|
2193
|
+
raise _create_syntax_error(201, pos, text)
|
|
2194
|
+
except UnexpectedToken:
|
|
2195
|
+
pass
|
|
2196
|
+
|
|
2197
|
+
# Default UnexpectedToken handling
|
|
2198
|
+
raise _create_generic_syntax_error(str(e), pos, text)
|
|
2199
|
+
|
|
2111
2200
|
environment = environment or (
|
|
2112
2201
|
Environment(working_path=root) if root else Environment()
|
|
2113
2202
|
)
|
|
@@ -2115,6 +2204,7 @@ def parse_text(
|
|
|
2115
2204
|
environment=environment, import_keys=["root"], parse_config=parse_config
|
|
2116
2205
|
)
|
|
2117
2206
|
start = datetime.now()
|
|
2207
|
+
|
|
2118
2208
|
try:
|
|
2119
2209
|
parser.set_text(text)
|
|
2120
2210
|
# disable fail on missing to allow for circular dependencies
|
|
@@ -2132,20 +2222,11 @@ def parse_text(
|
|
|
2132
2222
|
unpack_visit_error(e, text)
|
|
2133
2223
|
# this will never be reached
|
|
2134
2224
|
raise e
|
|
2135
|
-
except
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
ValidationError,
|
|
2141
|
-
TypeError,
|
|
2142
|
-
) as e:
|
|
2143
|
-
if isinstance(
|
|
2144
|
-
e, (UnexpectedCharacters, UnexpectedEOF, UnexpectedInput, UnexpectedToken)
|
|
2145
|
-
):
|
|
2146
|
-
raise InvalidSyntaxException(
|
|
2147
|
-
str(e) + "\nContext:\n" + e.get_context(text.replace("\n", " "), 20)
|
|
2148
|
-
)
|
|
2225
|
+
except UnexpectedToken as e:
|
|
2226
|
+
_handle_unexpected_token(e, text)
|
|
2227
|
+
except (UnexpectedCharacters, UnexpectedEOF, UnexpectedInput) as e:
|
|
2228
|
+
raise _create_generic_syntax_error(str(e), e.pos_in_stream or 0, text)
|
|
2229
|
+
except (ValidationError, TypeError) as e:
|
|
2149
2230
|
raise InvalidSyntaxException(str(e))
|
|
2150
2231
|
|
|
2151
2232
|
return environment, output
|
trilogy/parsing/trilogy.lark
CHANGED
|
@@ -130,7 +130,9 @@
|
|
|
130
130
|
|
|
131
131
|
over_list: concept_lit ("," concept_lit )* ","?
|
|
132
132
|
|
|
133
|
-
|
|
133
|
+
ORDERING_DIRECTION: /ASC|DESC/i
|
|
134
|
+
|
|
135
|
+
!ordering: ORDERING_DIRECTION ("NULLS"i /FIRST|LAST|AUTO/i )?
|
|
134
136
|
|
|
135
137
|
order_by: "ORDER"i "BY"i order_list
|
|
136
138
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|