pytrilogy 0.0.3.77__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.77.dist-info → pytrilogy-0.0.3.78.dist-info}/METADATA +10 -7
- {pytrilogy-0.0.3.77.dist-info → pytrilogy-0.0.3.78.dist-info}/RECORD +8 -8
- trilogy/__init__.py +1 -1
- trilogy/parsing/parse_engine.py +89 -51
- {pytrilogy-0.0.3.77.dist-info → pytrilogy-0.0.3.78.dist-info}/WHEEL +0 -0
- {pytrilogy-0.0.3.77.dist-info → pytrilogy-0.0.3.78.dist-info}/entry_points.txt +0 -0
- {pytrilogy-0.0.3.77.dist-info → pytrilogy-0.0.3.78.dist-info}/licenses/LICENSE.md +0 -0
- {pytrilogy-0.0.3.77.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,7 +97,7 @@ 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
102
|
trilogy/parsing/trilogy.lark,sha256=wiGXJdKfPTG7E_XdkN1rf9g9Yy1-UMVAXyTxtrBPm9w,15037
|
|
103
103
|
trilogy/scripts/__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
|
@@ -2093,12 +2093,50 @@ def parse_text_raw(text: str, environment: Optional[Environment] = None):
|
|
|
2093
2093
|
|
|
2094
2094
|
ERROR_CODES: dict[int, str] = {
|
|
2095
2095
|
# 100 code are SQL compatability errors
|
|
2096
|
-
101: "Trilogy does not have a FROM clause (Datasource resolution is automatic).
|
|
2096
|
+
101: "Using FROM keyword? Trilogy does not have a FROM clause (Datasource resolution is automatic).",
|
|
2097
2097
|
# 200 codes relate to required explicit syntax (we could loosen these?)
|
|
2098
|
-
201: 'Alias must be specified with "AS" - e.g. `SELECT x AS y`',
|
|
2099
|
-
210: "Order by must be explicit about direction - specify `asc` or `desc`.",
|
|
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
2100
|
}
|
|
2101
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
|
+
|
|
2102
2140
|
|
|
2103
2141
|
def parse_text(
|
|
2104
2142
|
text: str,
|
|
@@ -2117,6 +2155,48 @@ def parse_text(
|
|
|
2117
2155
|
| None
|
|
2118
2156
|
],
|
|
2119
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
|
+
|
|
2120
2200
|
environment = environment or (
|
|
2121
2201
|
Environment(working_path=root) if root else Environment()
|
|
2122
2202
|
)
|
|
@@ -2124,6 +2204,7 @@ def parse_text(
|
|
|
2124
2204
|
environment=environment, import_keys=["root"], parse_config=parse_config
|
|
2125
2205
|
)
|
|
2126
2206
|
start = datetime.now()
|
|
2207
|
+
|
|
2127
2208
|
try:
|
|
2128
2209
|
parser.set_text(text)
|
|
2129
2210
|
# disable fail on missing to allow for circular dependencies
|
|
@@ -2141,54 +2222,11 @@ def parse_text(
|
|
|
2141
2222
|
unpack_visit_error(e, text)
|
|
2142
2223
|
# this will never be reached
|
|
2143
2224
|
raise e
|
|
2144
|
-
except
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
ValidationError,
|
|
2150
|
-
TypeError,
|
|
2151
|
-
) as e:
|
|
2152
|
-
if isinstance(e, UnexpectedToken):
|
|
2153
|
-
if e.expected == {"ORDERING_DIRECTION"}:
|
|
2154
|
-
code = 210
|
|
2155
|
-
raise InvalidSyntaxException(
|
|
2156
|
-
f"Syntax [{code}]:"
|
|
2157
|
-
+ ERROR_CODES[code]
|
|
2158
|
-
+ "\nContext:\n"
|
|
2159
|
-
+ e.get_context(text.replace("\n", " "), 20)
|
|
2160
|
-
)
|
|
2161
|
-
parsed_tokens = (
|
|
2162
|
-
[x.value for x in e.token_history] if e.token_history else []
|
|
2163
|
-
)
|
|
2164
|
-
if parsed_tokens == ["FROM"]:
|
|
2165
|
-
code = 101
|
|
2166
|
-
raise InvalidSyntaxException(
|
|
2167
|
-
f"Syntax [{code}]:"
|
|
2168
|
-
+ ERROR_CODES[code]
|
|
2169
|
-
+ "\nContext:\n"
|
|
2170
|
-
+ e.get_context(text.replace("\n", " "), 20)
|
|
2171
|
-
)
|
|
2172
|
-
# recovery attempt for aliasing
|
|
2173
|
-
try:
|
|
2174
|
-
e.interactive_parser.feed_token(Token("AS", "AS"))
|
|
2175
|
-
e.interactive_parser.feed_token(e.token)
|
|
2176
|
-
raise InvalidSyntaxException(
|
|
2177
|
-
f"Syntax [{ERROR_CODES[201]}]:"
|
|
2178
|
-
+ ERROR_CODES[201]
|
|
2179
|
-
+ "\nContext:\n"
|
|
2180
|
-
+ e.get_context(text.replace("\n", " "), 20)
|
|
2181
|
-
)
|
|
2182
|
-
except UnexpectedToken:
|
|
2183
|
-
pass
|
|
2184
|
-
raise InvalidSyntaxException(
|
|
2185
|
-
str(e) + "\nContext:\n" + e.get_context(text.replace("\n", " "), 20)
|
|
2186
|
-
)
|
|
2187
|
-
elif isinstance(e, (UnexpectedCharacters, UnexpectedEOF, UnexpectedInput)):
|
|
2188
|
-
|
|
2189
|
-
raise InvalidSyntaxException(
|
|
2190
|
-
str(e) + "\nContext:\n" + e.get_context(text.replace("\n", " "), 20)
|
|
2191
|
-
)
|
|
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:
|
|
2192
2230
|
raise InvalidSyntaxException(str(e))
|
|
2193
2231
|
|
|
2194
2232
|
return environment, output
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|