pytrilogy 0.0.3.5__py3-none-any.whl → 0.0.3.7__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.5.dist-info → pytrilogy-0.0.3.7.dist-info}/METADATA +1 -1
- {pytrilogy-0.0.3.5.dist-info → pytrilogy-0.0.3.7.dist-info}/RECORD +16 -15
- trilogy/__init__.py +1 -1
- trilogy/core/functions.py +26 -6
- trilogy/core/models/datasource.py +6 -0
- trilogy/core/models/environment.py +4 -3
- trilogy/dialect/config.py +9 -0
- trilogy/dialect/dataframe.py +42 -0
- trilogy/dialect/enums.py +12 -1
- trilogy/engine.py +11 -4
- trilogy/executor.py +10 -2
- trilogy/parsing/parse_engine.py +24 -23
- {pytrilogy-0.0.3.5.dist-info → pytrilogy-0.0.3.7.dist-info}/LICENSE.md +0 -0
- {pytrilogy-0.0.3.5.dist-info → pytrilogy-0.0.3.7.dist-info}/WHEEL +0 -0
- {pytrilogy-0.0.3.5.dist-info → pytrilogy-0.0.3.7.dist-info}/entry_points.txt +0 -0
- {pytrilogy-0.0.3.5.dist-info → pytrilogy-0.0.3.7.dist-info}/top_level.txt +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
trilogy/__init__.py,sha256=
|
|
1
|
+
trilogy/__init__.py,sha256=nCVrjnf_bl_zZ7GmePdSoaRQ40QxFDkSFlafhQp8Cn8,302
|
|
2
2
|
trilogy/compiler.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
3
|
trilogy/constants.py,sha256=qZ1d0hoKPPV2HHCoFwPYTVB7b6bXjpWvXd3lE-zEhy8,1494
|
|
4
|
-
trilogy/engine.py,sha256=
|
|
5
|
-
trilogy/executor.py,sha256=
|
|
4
|
+
trilogy/engine.py,sha256=3etkm2RSVKO0IkgPKkrcs33X5gN_fIMyqMNfChcsR1E,1318
|
|
5
|
+
trilogy/executor.py,sha256=YgSCeeYVecI9526LGSLVe2apOo7Ddsttvs_nDC9yElQ,17194
|
|
6
6
|
trilogy/parser.py,sha256=o4cfk3j3yhUFoiDKq9ZX_GjBF3dKhDjXEwb63rcBkBM,293
|
|
7
7
|
trilogy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
8
|
trilogy/utility.py,sha256=euQccZLKoYBz0LNg5tzLlvv2YHvXh9HArnYp1V3uXsM,763
|
|
@@ -14,7 +14,7 @@ trilogy/core/env_processor.py,sha256=pFsxnluKIusGKx1z7tTnfsd_xZcPy9pZDungkjkyvI0
|
|
|
14
14
|
trilogy/core/environment_helpers.py,sha256=oOpewPwMp8xOtx2ayeeyuLNUwr-cli7UanHKot5ebNY,7627
|
|
15
15
|
trilogy/core/ergonomics.py,sha256=ASLDd0RqKWrZiG3XcKHo8nyTjaB_8xfE9t4NZ1UvGpc,1639
|
|
16
16
|
trilogy/core/exceptions.py,sha256=JPYyBcit3T_pRtlHdtKSeVJkIyWUTozW2aaut25A2xI,673
|
|
17
|
-
trilogy/core/functions.py,sha256=
|
|
17
|
+
trilogy/core/functions.py,sha256=rIkZGzw9hpIkXvuqQ1qPWFJO1W_NPYc6T9t0wTZ55M0,24784
|
|
18
18
|
trilogy/core/graph_models.py,sha256=z17EoO8oky2QOuO6E2aMWoVNKEVJFhLdsQZOhC4fNLU,2079
|
|
19
19
|
trilogy/core/internal.py,sha256=iicDBlC6nM8d7e7jqzf_ZOmpUsW8yrr2AA8AqEiLx-s,1577
|
|
20
20
|
trilogy/core/optimization.py,sha256=xGO8piVsLrpqrx-Aid_Y56_5slSv4eZmlP64hCHRiEc,7957
|
|
@@ -24,8 +24,8 @@ trilogy/core/models/author.py,sha256=oRCKWhz-i1fO1LlHWiHE3l1awCHdQ3yx6FKH9n9RxRU
|
|
|
24
24
|
trilogy/core/models/build.py,sha256=kiq31T8LtUtgmT37m617Q2MlMvQTuAxJzwb6947EiWU,56127
|
|
25
25
|
trilogy/core/models/build_environment.py,sha256=8UggvlPU708GZWYPJMc_ou2r7M3TY2g69eqGvz03YX0,5528
|
|
26
26
|
trilogy/core/models/core.py,sha256=yie1uuq62uOQ5fjob9NMJbdvQPrCErXUT7JTCuYRyjI,9697
|
|
27
|
-
trilogy/core/models/datasource.py,sha256=
|
|
28
|
-
trilogy/core/models/environment.py,sha256=
|
|
27
|
+
trilogy/core/models/datasource.py,sha256=6RjJUd2u4nYmEwFBpJlM9LbHVYDv8iHJxqiBMZqUrwI,9422
|
|
28
|
+
trilogy/core/models/environment.py,sha256=h06y1Dv7naw2GuFFAAyoFZmicG7a7Lu-dRoYPVfrOGo,25967
|
|
29
29
|
trilogy/core/models/execute.py,sha256=ABylFQgtavjjCfFkEsFdUwfMB4UBQLHjdzQ9E67QlAE,33521
|
|
30
30
|
trilogy/core/optimizations/__init__.py,sha256=EBanqTXEzf1ZEYjAneIWoIcxtMDite5-n2dQ5xcfUtg,356
|
|
31
31
|
trilogy/core/optimizations/base_optimization.py,sha256=gzDOKImoFn36k7XBD3ysEYDnbnb6vdVIztUfFQZsGnM,513
|
|
@@ -71,9 +71,10 @@ trilogy/dialect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
71
71
|
trilogy/dialect/base.py,sha256=u00kIIl98as1QzcduiiyyoBzxRGVeBxfeO5hWlRCAJU,40222
|
|
72
72
|
trilogy/dialect/bigquery.py,sha256=mKC3zoEU232h9RtIXJjqiZ72lWH8a6S28p6wAZKrAfg,2952
|
|
73
73
|
trilogy/dialect/common.py,sha256=cbTo_vamdp8pj9spSjGSH-bSZpy4FpNJ12k5vMvyT2Y,3942
|
|
74
|
-
trilogy/dialect/config.py,sha256=
|
|
74
|
+
trilogy/dialect/config.py,sha256=e-ZDVh7Z648JYz85JwSobTyo2cTi4lYGFMglZzB7atM,3184
|
|
75
|
+
trilogy/dialect/dataframe.py,sha256=ei5y91XyZHI3ydUbdQ2sInnw2qHGtgb21DNX6qff0xw,1419
|
|
75
76
|
trilogy/dialect/duckdb.py,sha256=2tH_OetgLJoKf_f4bdeeB0ozGC8f0h_xQ271I8qD-Oo,3690
|
|
76
|
-
trilogy/dialect/enums.py,sha256=
|
|
77
|
+
trilogy/dialect/enums.py,sha256=1KDgds_DC31hGxZzNI_TIggxXF7m9rIjn9KLgNf5WQU,4425
|
|
77
78
|
trilogy/dialect/postgres.py,sha256=VH4EB4myjIeZTHeFU6vK00GxY9c53rCBjg2mLbdaCEE,3254
|
|
78
79
|
trilogy/dialect/presto.py,sha256=bAxaDcLL21fivPg7hmBd3HJmd0yYJdPdwNgNA5ga7DE,3391
|
|
79
80
|
trilogy/dialect/snowflake.py,sha256=wmao9p26jX5yIX5SC8sRAZTXkPGTvq6ixO693QTfhz8,2989
|
|
@@ -88,14 +89,14 @@ trilogy/parsing/common.py,sha256=yAE3x4SyO4PfAb7HhZ_l9sNPYaf_pcM1K8ioEy76SCU,203
|
|
|
88
89
|
trilogy/parsing/config.py,sha256=Z-DaefdKhPDmSXLgg5V4pebhSB0h590vI0_VtHnlukI,111
|
|
89
90
|
trilogy/parsing/exceptions.py,sha256=92E5i2frv5hj9wxObJZsZqj5T6bglvPzvdvco_vW1Zk,38
|
|
90
91
|
trilogy/parsing/helpers.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
91
|
-
trilogy/parsing/parse_engine.py,sha256=
|
|
92
|
+
trilogy/parsing/parse_engine.py,sha256=32_yO_SreTjHxCkMziW2re15ilEZn01OUizVAvN9xHo,54656
|
|
92
93
|
trilogy/parsing/render.py,sha256=o_XuQWhcwx1lD9eGVqkqZEwkmQK0HdmWWokGBtdeH4I,17837
|
|
93
94
|
trilogy/parsing/trilogy.lark,sha256=EazfEvYPuvkPkNjUnVzFi0uD9baavugbSI8CyfawShk,12573
|
|
94
95
|
trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
95
96
|
trilogy/scripts/trilogy.py,sha256=1L0XrH4mVHRt1C9T1HnaDv2_kYEfbWTb5_-cBBke79w,3774
|
|
96
|
-
pytrilogy-0.0.3.
|
|
97
|
-
pytrilogy-0.0.3.
|
|
98
|
-
pytrilogy-0.0.3.
|
|
99
|
-
pytrilogy-0.0.3.
|
|
100
|
-
pytrilogy-0.0.3.
|
|
101
|
-
pytrilogy-0.0.3.
|
|
97
|
+
pytrilogy-0.0.3.7.dist-info/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
|
|
98
|
+
pytrilogy-0.0.3.7.dist-info/METADATA,sha256=wvr0oUtX0As37OC9ljg5XnV7rblzMNvUppA4il2PtPI,8983
|
|
99
|
+
pytrilogy-0.0.3.7.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
100
|
+
pytrilogy-0.0.3.7.dist-info/entry_points.txt,sha256=0petKryjvvtEfTlbZC1AuMFumH_WQ9v8A19LvoS6G6c,54
|
|
101
|
+
pytrilogy-0.0.3.7.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
|
|
102
|
+
pytrilogy-0.0.3.7.dist-info/RECORD,,
|
trilogy/__init__.py
CHANGED
trilogy/core/functions.py
CHANGED
|
@@ -503,32 +503,52 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
|
|
|
503
503
|
arg_count=1,
|
|
504
504
|
),
|
|
505
505
|
FunctionType.ADD: FunctionConfig(
|
|
506
|
-
valid_inputs={
|
|
506
|
+
valid_inputs={
|
|
507
|
+
DataType.INTEGER,
|
|
508
|
+
DataType.FLOAT,
|
|
509
|
+
DataType.NUMBER,
|
|
510
|
+
DataType.NUMERIC,
|
|
511
|
+
},
|
|
507
512
|
output_purpose=Purpose.PROPERTY,
|
|
508
513
|
output_type=DataType.INTEGER,
|
|
509
514
|
arg_count=InfiniteFunctionArgs,
|
|
510
515
|
),
|
|
511
516
|
FunctionType.SUBTRACT: FunctionConfig(
|
|
512
|
-
valid_inputs={
|
|
517
|
+
valid_inputs={
|
|
518
|
+
DataType.INTEGER,
|
|
519
|
+
DataType.FLOAT,
|
|
520
|
+
DataType.NUMBER,
|
|
521
|
+
DataType.NUMERIC,
|
|
522
|
+
},
|
|
513
523
|
output_purpose=Purpose.PROPERTY,
|
|
514
524
|
output_type=DataType.INTEGER,
|
|
515
525
|
arg_count=InfiniteFunctionArgs,
|
|
516
526
|
),
|
|
517
527
|
FunctionType.MULTIPLY: FunctionConfig(
|
|
518
|
-
valid_inputs={
|
|
528
|
+
valid_inputs={
|
|
529
|
+
DataType.INTEGER,
|
|
530
|
+
DataType.FLOAT,
|
|
531
|
+
DataType.NUMBER,
|
|
532
|
+
DataType.NUMERIC,
|
|
533
|
+
},
|
|
519
534
|
output_purpose=Purpose.PROPERTY,
|
|
520
535
|
output_type=DataType.INTEGER,
|
|
521
536
|
arg_count=InfiniteFunctionArgs,
|
|
522
537
|
),
|
|
523
538
|
FunctionType.DIVIDE: FunctionConfig(
|
|
524
|
-
valid_inputs={
|
|
539
|
+
valid_inputs={
|
|
540
|
+
DataType.INTEGER,
|
|
541
|
+
DataType.FLOAT,
|
|
542
|
+
DataType.NUMBER,
|
|
543
|
+
DataType.NUMERIC,
|
|
544
|
+
},
|
|
525
545
|
output_purpose=Purpose.PROPERTY,
|
|
526
546
|
output_type=DataType.INTEGER,
|
|
527
547
|
arg_count=InfiniteFunctionArgs,
|
|
528
548
|
),
|
|
529
549
|
FunctionType.MOD: FunctionConfig(
|
|
530
550
|
valid_inputs=[
|
|
531
|
-
{DataType.INTEGER, DataType.FLOAT, DataType.NUMBER},
|
|
551
|
+
{DataType.INTEGER, DataType.FLOAT, DataType.NUMBER, DataType.NUMERIC},
|
|
532
552
|
{DataType.INTEGER},
|
|
533
553
|
],
|
|
534
554
|
output_purpose=Purpose.PROPERTY,
|
|
@@ -537,7 +557,7 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
|
|
|
537
557
|
),
|
|
538
558
|
FunctionType.ROUND: FunctionConfig(
|
|
539
559
|
valid_inputs=[
|
|
540
|
-
{DataType.INTEGER, DataType.FLOAT, DataType.NUMBER},
|
|
560
|
+
{DataType.INTEGER, DataType.FLOAT, DataType.NUMBER, DataType.NUMERIC},
|
|
541
561
|
{DataType.INTEGER},
|
|
542
562
|
],
|
|
543
563
|
output_purpose=Purpose.PROPERTY,
|
|
@@ -117,6 +117,12 @@ class Datasource(HasUUID, Namespaced, BaseModel):
|
|
|
117
117
|
where: Optional[WhereClause] = None
|
|
118
118
|
non_partial_for: Optional[WhereClause] = None
|
|
119
119
|
|
|
120
|
+
@property
|
|
121
|
+
def safe_address(self) -> str:
|
|
122
|
+
if isinstance(self.address, Address):
|
|
123
|
+
return self.address.location
|
|
124
|
+
return self.address
|
|
125
|
+
|
|
120
126
|
def __eq__(self, other):
|
|
121
127
|
if not isinstance(other, Datasource):
|
|
122
128
|
return False
|
|
@@ -426,7 +426,6 @@ class Environment(BaseModel):
|
|
|
426
426
|
from trilogy.parsing.parse_engine import (
|
|
427
427
|
PARSER,
|
|
428
428
|
ParseToObjects,
|
|
429
|
-
gen_cache_lookup,
|
|
430
429
|
)
|
|
431
430
|
|
|
432
431
|
if isinstance(path, str):
|
|
@@ -440,7 +439,8 @@ class Environment(BaseModel):
|
|
|
440
439
|
else:
|
|
441
440
|
target = path
|
|
442
441
|
if not env:
|
|
443
|
-
|
|
442
|
+
import_keys = ["root", alias]
|
|
443
|
+
parse_address = "-".join(import_keys)
|
|
444
444
|
try:
|
|
445
445
|
with open(target, "r", encoding="utf-8") as f:
|
|
446
446
|
text = f.read()
|
|
@@ -454,11 +454,12 @@ class Environment(BaseModel):
|
|
|
454
454
|
),
|
|
455
455
|
parse_address=parse_address,
|
|
456
456
|
token_address=target,
|
|
457
|
+
import_keys=import_keys,
|
|
457
458
|
)
|
|
458
459
|
nparser.set_text(text)
|
|
459
460
|
nparser.environment.concepts.fail_on_missing = False
|
|
460
461
|
nparser.transform(PARSER.parse(text))
|
|
461
|
-
nparser.
|
|
462
|
+
nparser.run_second_parse_pass()
|
|
462
463
|
nparser.environment.concepts.fail_on_missing = True
|
|
463
464
|
|
|
464
465
|
except Exception as e:
|
trilogy/dialect/config.py
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
from pandas import DataFrame
|
|
2
|
+
|
|
3
|
+
|
|
1
4
|
class DialectConfig:
|
|
2
5
|
def __init__(self):
|
|
3
6
|
pass
|
|
@@ -104,3 +107,9 @@ class TrinoConfig(PrestoConfig):
|
|
|
104
107
|
if self.schema:
|
|
105
108
|
return f"trino://{self.username}:{self.password}@{self.host}:{self.port}/{self.catalog}/{self.schema}"
|
|
106
109
|
return f"trino://{self.username}:{self.password}@{self.host}:{self.port}/{self.catalog}"
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class DataFrameConfig(DuckDBConfig):
|
|
113
|
+
def __init__(self, dataframes: dict[str, DataFrame]):
|
|
114
|
+
super().__init__()
|
|
115
|
+
self.dataframes = dataframes
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from pandas import DataFrame
|
|
4
|
+
from sqlalchemy import text
|
|
5
|
+
|
|
6
|
+
from trilogy.core.models.environment import Environment
|
|
7
|
+
from trilogy.dialect.duckdb import DuckDBDialect
|
|
8
|
+
from trilogy.engine import ExecutionEngine
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DataframeDialect(DuckDBDialect):
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class DataframeConnectionWrapper(ExecutionEngine):
|
|
16
|
+
def __init__(self, engine: ExecutionEngine, dataframes: dict[str, DataFrame]):
|
|
17
|
+
self.engine = engine
|
|
18
|
+
self.dataframes = dataframes
|
|
19
|
+
self.connection = None
|
|
20
|
+
|
|
21
|
+
def setup(self, env: Environment, connection):
|
|
22
|
+
self._register_dataframes(env, connection)
|
|
23
|
+
|
|
24
|
+
def _register_dataframes(self, env: Environment, connection):
|
|
25
|
+
for ds in env.datasources.values():
|
|
26
|
+
if ds.safe_address in self.dataframes:
|
|
27
|
+
connection.execute(
|
|
28
|
+
text("register(:name, :df)"),
|
|
29
|
+
{"name": ds.safe_address, "df": self.dataframes[ds.safe_address]},
|
|
30
|
+
)
|
|
31
|
+
else:
|
|
32
|
+
raise ValueError(
|
|
33
|
+
f"Dataframe {ds.safe_address} not found in dataframes on connection config, have {self.dataframes.keys()}"
|
|
34
|
+
)
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
def add_dataframe(self, name: str, df: DataFrame, connection, env: Environment):
|
|
38
|
+
self.dataframes[name] = df
|
|
39
|
+
self._register_dataframes(env, connection)
|
|
40
|
+
|
|
41
|
+
def connect(self) -> Any:
|
|
42
|
+
return self.engine.connect()
|
trilogy/dialect/enums.py
CHANGED
|
@@ -16,7 +16,7 @@ def default_factory(conf: DialectConfig, config_type):
|
|
|
16
16
|
|
|
17
17
|
if not isinstance(conf, config_type):
|
|
18
18
|
raise TypeError(
|
|
19
|
-
f"Invalid dialect configuration for type {type(config_type).__name__}"
|
|
19
|
+
f"Invalid dialect configuration for type {type(config_type).__name__}, is {type(conf)}"
|
|
20
20
|
)
|
|
21
21
|
if conf.connect_args:
|
|
22
22
|
return create_engine(
|
|
@@ -33,6 +33,7 @@ class Dialects(Enum):
|
|
|
33
33
|
TRINO = "trino"
|
|
34
34
|
POSTGRES = "postgres"
|
|
35
35
|
SNOWFLAKE = "snowflake"
|
|
36
|
+
DATAFRAME = "dataframe"
|
|
36
37
|
|
|
37
38
|
@classmethod
|
|
38
39
|
def _missing_(cls, value):
|
|
@@ -88,6 +89,16 @@ class Dialects(Enum):
|
|
|
88
89
|
from trilogy.dialect.config import TrinoConfig
|
|
89
90
|
|
|
90
91
|
return _engine_factory(conf, TrinoConfig)
|
|
92
|
+
elif self == Dialects.DATAFRAME:
|
|
93
|
+
from trilogy.dialect.config import DataFrameConfig
|
|
94
|
+
from trilogy.dialect.dataframe import DataframeConnectionWrapper
|
|
95
|
+
|
|
96
|
+
if not conf:
|
|
97
|
+
conf = DataFrameConfig(dataframes={})
|
|
98
|
+
|
|
99
|
+
base = _engine_factory(conf, DataFrameConfig)
|
|
100
|
+
|
|
101
|
+
return DataframeConnectionWrapper(base, dataframes=conf.dataframes)
|
|
91
102
|
else:
|
|
92
103
|
raise ValueError(
|
|
93
104
|
f"Unsupported dialect {self} for default engine creation; create one explicitly."
|
trilogy/engine.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
from typing import Protocol
|
|
1
|
+
from typing import Any, Protocol
|
|
2
2
|
|
|
3
3
|
from sqlalchemy.engine import Connection, CursorResult, Engine
|
|
4
4
|
|
|
5
|
+
from trilogy.core.models.environment import Environment
|
|
6
|
+
|
|
5
7
|
|
|
6
8
|
class EngineResult(Protocol):
|
|
7
9
|
pass
|
|
@@ -13,7 +15,7 @@ class EngineResult(Protocol):
|
|
|
13
15
|
class EngineConnection(Protocol):
|
|
14
16
|
pass
|
|
15
17
|
|
|
16
|
-
def execute(self, statement: str) -> EngineResult:
|
|
18
|
+
def execute(self, statement: str, parameters: Any | None = None) -> EngineResult:
|
|
17
19
|
pass
|
|
18
20
|
|
|
19
21
|
|
|
@@ -23,6 +25,9 @@ class ExecutionEngine(Protocol):
|
|
|
23
25
|
def connect(self) -> EngineConnection:
|
|
24
26
|
pass
|
|
25
27
|
|
|
28
|
+
def setup(self, env: Environment, connection):
|
|
29
|
+
pass
|
|
30
|
+
|
|
26
31
|
|
|
27
32
|
### Begin default SQLAlchemy implementation
|
|
28
33
|
class SqlAlchemyResult(EngineResult):
|
|
@@ -37,8 +42,10 @@ class SqlAlchemyConnection(EngineConnection):
|
|
|
37
42
|
def __init__(self, connection: Connection):
|
|
38
43
|
self.connection = connection
|
|
39
44
|
|
|
40
|
-
def execute(
|
|
41
|
-
|
|
45
|
+
def execute(
|
|
46
|
+
self, statement: str, parameters: Any | None = None
|
|
47
|
+
) -> SqlAlchemyResult:
|
|
48
|
+
return SqlAlchemyResult(self.connection.execute(statement, parameters))
|
|
42
49
|
|
|
43
50
|
|
|
44
51
|
class SqlAlchemyEngine(ExecutionEngine):
|
trilogy/executor.py
CHANGED
|
@@ -4,7 +4,7 @@ from pathlib import Path
|
|
|
4
4
|
from typing import Any, Generator, List, Optional, Protocol
|
|
5
5
|
|
|
6
6
|
from sqlalchemy import text
|
|
7
|
-
from sqlalchemy.engine import CursorResult
|
|
7
|
+
from sqlalchemy.engine import CursorResult
|
|
8
8
|
|
|
9
9
|
from trilogy.constants import logger
|
|
10
10
|
from trilogy.core.enums import FunctionType, Granularity, IOType
|
|
@@ -33,6 +33,7 @@ from trilogy.core.statements.execute import (
|
|
|
33
33
|
)
|
|
34
34
|
from trilogy.dialect.base import BaseDialect
|
|
35
35
|
from trilogy.dialect.enums import Dialects
|
|
36
|
+
from trilogy.engine import ExecutionEngine
|
|
36
37
|
from trilogy.hooks.base_hook import BaseHook
|
|
37
38
|
from trilogy.parser import parse_text
|
|
38
39
|
|
|
@@ -71,7 +72,7 @@ class Executor(object):
|
|
|
71
72
|
def __init__(
|
|
72
73
|
self,
|
|
73
74
|
dialect: Dialects,
|
|
74
|
-
engine:
|
|
75
|
+
engine: ExecutionEngine,
|
|
75
76
|
environment: Optional[Environment] = None,
|
|
76
77
|
hooks: List[BaseHook] | None = None,
|
|
77
78
|
):
|
|
@@ -109,9 +110,16 @@ class Executor(object):
|
|
|
109
110
|
from trilogy.dialect.snowflake import SnowflakeDialect
|
|
110
111
|
|
|
111
112
|
self.generator = SnowflakeDialect()
|
|
113
|
+
elif self.dialect == Dialects.DATAFRAME:
|
|
114
|
+
from trilogy.dialect.dataframe import DataframeDialect
|
|
115
|
+
|
|
116
|
+
self.generator = DataframeDialect()
|
|
112
117
|
else:
|
|
113
118
|
raise ValueError(f"Unsupported dialect {self.dialect}")
|
|
114
119
|
self.connection = self.engine.connect()
|
|
120
|
+
# TODO: make generic
|
|
121
|
+
if self.dialect == Dialects.DATAFRAME:
|
|
122
|
+
self.engine.setup(self.environment, self.connection)
|
|
115
123
|
|
|
116
124
|
def execute_statement(self, statement) -> Optional[CursorResult]:
|
|
117
125
|
if not isinstance(
|
trilogy/parsing/parse_engine.py
CHANGED
|
@@ -129,6 +129,8 @@ CONSTANT_TYPES = (int, float, str, bool, list, ListWrapper, MapWrapper)
|
|
|
129
129
|
|
|
130
130
|
SELF_LABEL = "root"
|
|
131
131
|
|
|
132
|
+
MAX_PARSE_DEPTH = 10
|
|
133
|
+
|
|
132
134
|
|
|
133
135
|
@dataclass
|
|
134
136
|
class WholeGrainWrapper:
|
|
@@ -146,13 +148,6 @@ with open(join(dirname(__file__), "trilogy.lark"), "r") as f:
|
|
|
146
148
|
)
|
|
147
149
|
|
|
148
150
|
|
|
149
|
-
def gen_cache_lookup(path: str, alias: str, parent: str) -> str:
|
|
150
|
-
# path is the path of the file
|
|
151
|
-
# alias is what it's being imported under
|
|
152
|
-
# parent is the...direct parnet?
|
|
153
|
-
return path + alias + parent
|
|
154
|
-
|
|
155
|
-
|
|
156
151
|
def parse_concept_reference(
|
|
157
152
|
name: str, environment: Environment, purpose: Optional[Purpose] = None
|
|
158
153
|
) -> Tuple[str, str, str, str | None]:
|
|
@@ -226,6 +221,8 @@ class ParseToObjects(Transformer):
|
|
|
226
221
|
parsed: dict[str, "ParseToObjects"] | None = None,
|
|
227
222
|
tokens: dict[Path | str, ParseTree] | None = None,
|
|
228
223
|
text_lookup: dict[Path | str, str] | None = None,
|
|
224
|
+
environment_lookup: dict[str, Environment] | None = None,
|
|
225
|
+
import_keys: list[str] | None = None,
|
|
229
226
|
):
|
|
230
227
|
Transformer.__init__(self, True)
|
|
231
228
|
self.environment: Environment = environment
|
|
@@ -233,6 +230,7 @@ class ParseToObjects(Transformer):
|
|
|
233
230
|
self.token_address: Path | str = token_address or SELF_LABEL
|
|
234
231
|
self.parsed: dict[str, ParseToObjects] = parsed if parsed is not None else {}
|
|
235
232
|
self.tokens: dict[Path | str, ParseTree] = tokens if tokens is not None else {}
|
|
233
|
+
self.environments: dict[str, Environment] = environment_lookup or {}
|
|
236
234
|
self.text_lookup: dict[Path | str, str] = (
|
|
237
235
|
text_lookup if text_lookup is not None else {}
|
|
238
236
|
)
|
|
@@ -240,6 +238,7 @@ class ParseToObjects(Transformer):
|
|
|
240
238
|
# after initial parsing
|
|
241
239
|
self.parse_pass = ParsePass.INITIAL
|
|
242
240
|
self.function_factory = FunctionFactory(self.environment)
|
|
241
|
+
self.import_keys: list[str] = import_keys or ["root"]
|
|
243
242
|
|
|
244
243
|
def set_text(self, text: str):
|
|
245
244
|
self.text_lookup[self.token_address] = text
|
|
@@ -255,14 +254,14 @@ class ParseToObjects(Transformer):
|
|
|
255
254
|
for _, v in self.parsed.items():
|
|
256
255
|
v.prepare_parse()
|
|
257
256
|
|
|
258
|
-
def
|
|
257
|
+
def run_second_parse_pass(self, force: bool = False):
|
|
259
258
|
if self.token_address not in self.tokens:
|
|
260
259
|
return []
|
|
261
260
|
self.parse_pass = ParsePass.VALIDATION
|
|
262
261
|
for _, v in list(self.parsed.items()):
|
|
263
|
-
if v.parse_pass == ParsePass.VALIDATION
|
|
262
|
+
if v.parse_pass == ParsePass.VALIDATION:
|
|
264
263
|
continue
|
|
265
|
-
v.
|
|
264
|
+
v.run_second_parse_pass()
|
|
266
265
|
reparsed = self.transform(self.tokens[self.token_address])
|
|
267
266
|
self.environment.concepts.undefined = {}
|
|
268
267
|
return reparsed
|
|
@@ -306,11 +305,6 @@ class ParseToObjects(Transformer):
|
|
|
306
305
|
def QUOTED_IDENTIFIER(self, args) -> str:
|
|
307
306
|
return args.value[1:-1]
|
|
308
307
|
|
|
309
|
-
# @v_args(meta=True)
|
|
310
|
-
# def concept_lit(self, meta: Meta, args) -> ConceptRef:
|
|
311
|
-
# address = args[0]
|
|
312
|
-
# return self.environment.concepts.__getitem__(address, meta.line)
|
|
313
|
-
# return ConceptRef(address=address, line_no=meta.line)
|
|
314
308
|
@v_args(meta=True)
|
|
315
309
|
def concept_lit(self, meta: Meta, args) -> ConceptRef:
|
|
316
310
|
address = args[0]
|
|
@@ -402,7 +396,6 @@ class ParseToObjects(Transformer):
|
|
|
402
396
|
if len(concept_list) > 1:
|
|
403
397
|
modifiers += concept_list[:-1]
|
|
404
398
|
concept = concept_list[-1]
|
|
405
|
-
assert not self.environment.concepts.fail_on_missing
|
|
406
399
|
resolved = self.environment.concepts.__getitem__( # type: ignore
|
|
407
400
|
key=concept, line_no=meta.line, file=self.token_address
|
|
408
401
|
)
|
|
@@ -858,8 +851,10 @@ class ParseToObjects(Transformer):
|
|
|
858
851
|
def import_statement(self, args: list[str]) -> ImportStatement:
|
|
859
852
|
if len(args) == 2:
|
|
860
853
|
alias = args[-1]
|
|
854
|
+
cache_key = args[-1]
|
|
861
855
|
else:
|
|
862
856
|
alias = self.environment.namespace
|
|
857
|
+
cache_key = args[0]
|
|
863
858
|
path = args[0].split(".")
|
|
864
859
|
|
|
865
860
|
target = join(self.environment.working_path, *path) + ".preql"
|
|
@@ -867,10 +862,14 @@ class ParseToObjects(Transformer):
|
|
|
867
862
|
# tokens + text are cached by path
|
|
868
863
|
token_lookup = Path(target)
|
|
869
864
|
|
|
870
|
-
#
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
865
|
+
# parser + env has to be cached by prior import path + current key
|
|
866
|
+
key_path = self.import_keys + [cache_key]
|
|
867
|
+
cache_lookup = "-".join(key_path)
|
|
868
|
+
|
|
869
|
+
# we don't iterate past the max parse depth
|
|
870
|
+
if len(key_path) > MAX_PARSE_DEPTH:
|
|
871
|
+
return ImportStatement(alias=alias, path=Path(target))
|
|
872
|
+
|
|
874
873
|
if token_lookup in self.tokens:
|
|
875
874
|
raw_tokens = self.tokens[token_lookup]
|
|
876
875
|
text = self.text_lookup[token_lookup]
|
|
@@ -886,7 +885,7 @@ class ParseToObjects(Transformer):
|
|
|
886
885
|
new_env = nparser.environment
|
|
887
886
|
if nparser.parse_pass != ParsePass.VALIDATION:
|
|
888
887
|
# nparser.transform(raw_tokens)
|
|
889
|
-
nparser.
|
|
888
|
+
nparser.run_second_parse_pass()
|
|
890
889
|
else:
|
|
891
890
|
try:
|
|
892
891
|
new_env = Environment(
|
|
@@ -902,6 +901,7 @@ class ParseToObjects(Transformer):
|
|
|
902
901
|
parsed=self.parsed,
|
|
903
902
|
tokens=self.tokens,
|
|
904
903
|
text_lookup=self.text_lookup,
|
|
904
|
+
import_keys=self.import_keys + [cache_key],
|
|
905
905
|
)
|
|
906
906
|
nparser.transform(raw_tokens)
|
|
907
907
|
self.parsed[cache_lookup] = nparser
|
|
@@ -909,6 +909,7 @@ class ParseToObjects(Transformer):
|
|
|
909
909
|
raise ImportError(
|
|
910
910
|
f"Unable to import file {target}, parsing error: {e}"
|
|
911
911
|
) from e
|
|
912
|
+
|
|
912
913
|
parsed_path = Path(args[0])
|
|
913
914
|
imps = ImportStatement(alias=alias, path=parsed_path)
|
|
914
915
|
|
|
@@ -1621,7 +1622,7 @@ def parse_text(
|
|
|
1621
1622
|
environment = environment or (
|
|
1622
1623
|
Environment(working_path=root) if root else Environment()
|
|
1623
1624
|
)
|
|
1624
|
-
parser = ParseToObjects(environment=environment)
|
|
1625
|
+
parser = ParseToObjects(environment=environment, import_keys=["root"])
|
|
1625
1626
|
|
|
1626
1627
|
try:
|
|
1627
1628
|
parser.set_text(text)
|
|
@@ -1629,7 +1630,7 @@ def parse_text(
|
|
|
1629
1630
|
parser.prepare_parse()
|
|
1630
1631
|
parser.transform(PARSER.parse(text))
|
|
1631
1632
|
# this will reset fail on missing
|
|
1632
|
-
pass_two = parser.
|
|
1633
|
+
pass_two = parser.run_second_parse_pass()
|
|
1633
1634
|
output = [v for v in pass_two if v]
|
|
1634
1635
|
environment.concepts.fail_on_missing = True
|
|
1635
1636
|
except VisitError as e:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|