pytrilogy 0.0.3.112__py3-none-any.whl → 0.0.3.115__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.112
3
+ Version: 0.0.3.115
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Classifier: Programming Language :: Python
6
6
  Classifier: Programming Language :: Python :: 3
@@ -51,61 +51,41 @@ The Trilogy language is an experiment in better SQL for analytics - a streamline
51
51
 
52
52
  Trilogy is especially powerful for data consumption, providing a rich metadata layer that makes creating, interpreting, and visualizing queries easy and expressive.
53
53
 
54
+
55
+ We recommend starting with the studio to explore Trilogy. For 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.
56
+
57
+
54
58
  ## Quick Start
55
59
 
56
60
  > [!TIP]
57
61
  > **Try it now:** [Open-source studio](https://trilogydata.dev/trilogy-studio-core/) | [Interactive demo](https://trilogydata.dev/demo/) | [Documentation](https://trilogydata.dev/)
58
62
 
59
- **Install locally:**
63
+ **Install**
60
64
  ```bash
61
65
  pip install pytrilogy
62
66
  ```
63
67
 
64
- **Your first query:**
68
+ **Save in hello.preql**
65
69
  ```sql
66
- # Save as hello.preql
67
- import names;
68
-
69
- const top_names <- ['Elvis', 'Elvira', 'Elrond', 'Sam'];
70
+ const prime <- unnest([2, 3, 5, 7, 11, 13, 17, 19, 23, 29]);
70
71
 
71
- def initcap(word) -> upper(substring(word, 1, 1)) || substring(word, 2, len(word));
72
+ def cube_plus_one(x) -> (x * x * x + 1);
72
73
 
73
74
  WHERE
74
- @initcap(name) in top_names
75
+ prime_cubed_plus_one % 7 = 0
75
76
  SELECT
76
- name,
77
- sum(births) as name_count
77
+ prime,
78
+ @cube_plus_one(prime) as prime_cubed_plus_one
78
79
  ORDER BY
79
- name_count desc
80
+ prime asc
80
81
  LIMIT 10;
81
82
  ```
82
83
 
83
- **Run it:**
84
+ **Run it in DuckDB**
84
85
  ```bash
85
86
  trilogy run hello.preql duckdb
86
87
  ```
87
88
 
88
- We recommend starting with the studio to explore Trilogy. For 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.
89
-
90
- ## Trilogy Looks Like SQL
91
-
92
- ```sql
93
- import names;
94
-
95
- const top_names <- ['Elvis', 'Elvira', 'Elrond', 'Sam'];
96
-
97
- def initcap(word) -> upper(substring(word, 1, 1)) || substring(word, 2, len(word));
98
-
99
- WHERE
100
- @initcap(name) in top_names
101
- SELECT
102
- name,
103
- sum(births) as name_count
104
- ORDER BY
105
- name_count desc
106
- LIMIT 10;
107
- ```
108
-
109
89
  ## Trilogy is Easy to Write
110
90
  For humans *and* AI. Enjoy flexible, one-shot query generation without any DB access or security risks.
111
91
 
@@ -1,6 +1,6 @@
1
- pytrilogy-0.0.3.112.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
2
- trilogy/__init__.py,sha256=KpC5q9qz6KQi0DSThY-iVF-bEm7ue6IBVE2nlKbQJHk,304
3
- trilogy/constants.py,sha256=g_zkVCNjGop6coZ1kM8eXXAzCnUN22ldx3TYFz0E9sc,1747
1
+ pytrilogy-0.0.3.115.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
2
+ trilogy/__init__.py,sha256=L11AizxIDnbCCjCvAE55VTsy0d4QmvAZfNp61uAPu00,304
3
+ trilogy/constants.py,sha256=2ERigKZBmULg3Qr43nWKgdwqiCnT8XPQVeH-O-k66wc,2640
4
4
  trilogy/engine.py,sha256=v4TpNktM4zZ9OX7jZH2nde4dpX5uAH2U23ELfULTCSg,2280
5
5
  trilogy/executor.py,sha256=q3EsAjzgxNxPn-yTHD_FTFzm7bJ2mlf9CrJEjyt6-pE,17884
6
6
  trilogy/parser.py,sha256=o4cfk3j3yhUFoiDKq9ZX_GjBF3dKhDjXEwb63rcBkBM,293
@@ -23,24 +23,24 @@ trilogy/ai/providers/utils.py,sha256=yttP6y2E_XzdytBCwhaKekfXfxM6gE6MRce4AtyLL60
23
23
  trilogy/authoring/__init__.py,sha256=TABMOETSMERrWuyDLR0nK4ISlqR0yaqeXrmuOdrSvAY,3060
24
24
  trilogy/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
25
  trilogy/core/constants.py,sha256=nizWYDCJQ1bigQMtkNIEMNTcN0NoEAXiIHLzpelxQ24,201
26
- trilogy/core/enums.py,sha256=H8I2Dz4POHZ4ixYCGzNs4c3KDqxLQklGLVfmje1DSMo,8877
26
+ trilogy/core/enums.py,sha256=R_iNsK8j0uGJ5fzfFUwoe1vhIHTX28VJfDQd99WBQ4E,9064
27
27
  trilogy/core/env_processor.py,sha256=H-rr2ALj31l5oh3FqeI47Qju6OOfiXBacXNJGNZ92zQ,4521
28
28
  trilogy/core/environment_helpers.py,sha256=TRlqVctqIRBxzfjRBmpQsAVoiCcsEKBhG1B6PUE0l1M,12743
29
29
  trilogy/core/ergonomics.py,sha256=e-7gE29vPLFdg0_A1smQ7eOrUwKl5VYdxRSTddHweRA,1631
30
30
  trilogy/core/exceptions.py,sha256=axkVXYJYQXCCwMHwlyDA232g4tCOwdCZUt7eHeUMDMg,2829
31
- trilogy/core/functions.py,sha256=sdV6Z3NUVfwL1d18eNcaAXllVNqzLez23McsJ6xIp7M,33182
31
+ trilogy/core/functions.py,sha256=hURJBM5dNcHfXuPHaHM_tz_ahsEktnmL4At0zebfjMk,34668
32
32
  trilogy/core/graph_models.py,sha256=4EWFTHGfYd72zvS2HYoV6hm7nMC_VEd7vWr6txY-ig0,3400
33
33
  trilogy/core/internal.py,sha256=r9QagDB2GvpqlyD_I7VrsfbVfIk5mnok2znEbv72Aa4,2681
34
34
  trilogy/core/optimization.py,sha256=Km0ITEx9n6Iv5ReX6tm4uXO5uniSv_ooahycNNiET3g,9212
35
35
  trilogy/core/query_processor.py,sha256=rMrtLSQxVm7yeyh0nWjDNI9nnu4Xi0NgHvBJ14gvu4I,20384
36
36
  trilogy/core/utility.py,sha256=3VC13uSQWcZNghgt7Ot0ZTeEmNqs__cx122abVq9qhM,410
37
37
  trilogy/core/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
- trilogy/core/models/author.py,sha256=3I7PFpJgoQT9RPOT3DfiqAjEtkcQPJnScs60I2UoyWo,81461
39
- trilogy/core/models/build.py,sha256=iqk_-3plxX1BdxvUCTebqE9F3x62f40neKGf6Ld4VVU,70858
38
+ trilogy/core/models/author.py,sha256=DFpIGlIruz0JYIOLJ0qZQrsdT4BvQX0augow1G8pD54,81841
39
+ trilogy/core/models/build.py,sha256=LN8G1JRk8RDSFQwqS3CUszIIUgB5v9Nxy6EpxvAcJ1A,71514
40
40
  trilogy/core/models/build_environment.py,sha256=mpx7MKGc60fnZLVdeLi2YSREy7eQbQYycCrP4zF-rHU,5258
41
41
  trilogy/core/models/core.py,sha256=iT9WdZoiXeglmUHWn6bZyXCTBpkApTGPKtNm_Mhbu_g,12987
42
42
  trilogy/core/models/datasource.py,sha256=wogTevZ-9CyUW2a8gjzqMCieircxi-J5lkI7EOAZnck,9596
43
- trilogy/core/models/environment.py,sha256=hwTIRnJgaHUdCYof7U5A9NPitGZ2s9yxqiW5O2SaJ9Y,28759
43
+ trilogy/core/models/environment.py,sha256=do1Xvr9oyjDj0knAxgIqexSbt0HMrHbLJNyl9utkSvs,28760
44
44
  trilogy/core/models/execute.py,sha256=3fgEdho2e7S0outq91cCzb9jFwz6L1hTbsTrJwGvIFs,42311
45
45
  trilogy/core/optimizations/__init__.py,sha256=yspWc25M5SgAuvXYoSt5J8atyPbDlOfsKjIo5yGD9s4,368
46
46
  trilogy/core/optimizations/base_optimization.py,sha256=gzDOKImoFn36k7XBD3ysEYDnbnb6vdVIztUfFQZsGnM,513
@@ -48,9 +48,9 @@ trilogy/core/optimizations/hide_unused_concept.py,sha256=DbsP8NqQOxmPv9omDOoFNPU
48
48
  trilogy/core/optimizations/inline_datasource.py,sha256=2sWNRpoRInnTgo9wExVT_r9RfLAQHI57reEV5cGHUcg,4329
49
49
  trilogy/core/optimizations/predicate_pushdown.py,sha256=5ubatgq1IwWQ4L2FDt4--y168YLuGP-vwqH0m8IeTIw,9786
50
50
  trilogy/core/processing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
51
- trilogy/core/processing/concept_strategies_v3.py,sha256=-iC2CLALmSrOglMKZM4TslVncyOrJbUH0V_COmbqHIw,22681
52
- trilogy/core/processing/discovery_node_factory.py,sha256=p23jiiHyhrW-Q8ndbnRlqMHJKT8ZqPOA89SzE4xaFFo,15445
53
- trilogy/core/processing/discovery_utility.py,sha256=BfzeliD0CQ3x3MRLpw3msibGc7rOPh8aH_gbfuchAIs,13352
51
+ trilogy/core/processing/concept_strategies_v3.py,sha256=Y6AG10NGU_mUiTfXmxkss7QSO5iLpOdzGN6-7Hc5XiQ,23294
52
+ trilogy/core/processing/discovery_node_factory.py,sha256=llnLxZo1NqBRIuuPz0GUohym6LZFhVkPT3xSiORi3k4,15446
53
+ trilogy/core/processing/discovery_utility.py,sha256=mO0npZMRlQSzxt3l4m8garKBAOrXFkzt3eiiUyUSoIU,13528
54
54
  trilogy/core/processing/discovery_validation.py,sha256=eZ4HfHMpqZLI8MGG2jez8arS8THs6ceuVrQFIY6gXrU,5364
55
55
  trilogy/core/processing/graph_utils.py,sha256=8QUVrkE9j-9C1AyrCb1nQEh8daCe0u1HuXl-Te85lag,1205
56
56
  trilogy/core/processing/utility.py,sha256=ESs6pKqVP2c9eMdfB2JNjw7D7YnoezVwbLFx1D6OUYA,26088
@@ -58,7 +58,7 @@ trilogy/core/processing/node_generators/__init__.py,sha256=iVJ-crowPxYeut-hFjyEj
58
58
  trilogy/core/processing/node_generators/basic_node.py,sha256=rv53il-W633by0plvbN5qaqznJfl60yPinLZGBAC_MI,5707
59
59
  trilogy/core/processing/node_generators/common.py,sha256=xF32Kf6B08dZgKs2SOow1HomptSiSC057GCUCHFlS5s,9464
60
60
  trilogy/core/processing/node_generators/constant_node.py,sha256=LfpDq2WrBRZ3tGsLxw77LuigKfhbteWWh9L8BGdMGwk,1146
61
- trilogy/core/processing/node_generators/filter_node.py,sha256=cJ5od1fAfvalaUDO2O4Y6Yrr2RukOCqey7f3zrKSBbI,10808
61
+ trilogy/core/processing/node_generators/filter_node.py,sha256=EiP_tafx-X0gM-BIVRCy2rnp1_apt2cbhVfv8cg9dVg,11259
62
62
  trilogy/core/processing/node_generators/group_node.py,sha256=sIm1QYrF4EY6sk56A48B6MieCZqvaJLSQebih_aiKnQ,8567
63
63
  trilogy/core/processing/node_generators/group_to_node.py,sha256=jKcNCDOY6fNblrdZwaRU0sbUSr9H0moQbAxrGgX6iGA,3832
64
64
  trilogy/core/processing/node_generators/multiselect_node.py,sha256=dHPDoSKU0FF6Ue_t_LkZxTd0Q-Sf-EpYdsMYdyUlFQc,7120
@@ -69,7 +69,7 @@ trilogy/core/processing/node_generators/select_merge_node.py,sha256=9MZdQJtkoe5N
69
69
  trilogy/core/processing/node_generators/select_node.py,sha256=Ta1G39V94gjX_AgyZDz9OqnwLz4BjY3D6Drx9YpziMQ,3555
70
70
  trilogy/core/processing/node_generators/synonym_node.py,sha256=AnAsa_Wj50NJ_IK0HSgab_7klYmKVrv0WI1uUe-GvEY,3766
71
71
  trilogy/core/processing/node_generators/union_node.py,sha256=NxQbnRRoYMI4WjMeph41yk4E6yipj53qdGuNt-Mozxw,2818
72
- trilogy/core/processing/node_generators/unnest_node.py,sha256=u_hVHFYMz-ZylDdHH9mhFSRpxuKcTGvrrOP0rxrY_Xg,3901
72
+ trilogy/core/processing/node_generators/unnest_node.py,sha256=Q9E6MF6uVLqP15tlx_Bwa6mIWSNW68LJpkSYyr_TArs,6229
73
73
  trilogy/core/processing/node_generators/window_node.py,sha256=wNvmumGO6AIQ7C9bDUYYZ6LJvDvPQPfFVX82pTxjV-k,6767
74
74
  trilogy/core/processing/node_generators/select_helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
75
75
  trilogy/core/processing/node_generators/select_helpers/datasource_injection.py,sha256=m2YQ4OmG0N2O61a7NEq1ZzbTa7JsCC00lxB2ymjcYRI,8224
@@ -84,7 +84,7 @@ trilogy/core/processing/nodes/union_node.py,sha256=hLAXXVWqEgMWi7dlgSHfCF59fon64
84
84
  trilogy/core/processing/nodes/unnest_node.py,sha256=oLKMMNMx6PLDPlt2V5neFMFrFWxET8r6XZElAhSNkO0,2181
85
85
  trilogy/core/processing/nodes/window_node.py,sha256=JXJ0iVRlSEM2IBr1TANym2RaUf_p5E_l2sNykRzXWDo,1710
86
86
  trilogy/core/statements/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
87
- trilogy/core/statements/author.py,sha256=VFzylve72fw0tqMSP5Yiwp8--_r92b9zzX1XAdxuTYQ,15963
87
+ trilogy/core/statements/author.py,sha256=zLd-1IZKF5UE7oWQYqifn3b3JcW2wCbonlELjJiZhfo,16436
88
88
  trilogy/core/statements/build.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
89
89
  trilogy/core/statements/common.py,sha256=VnVLULQg1TJLNUFzJaROT1tsf2ewk3IpuhvZaP36R6A,535
90
90
  trilogy/core/statements/execute.py,sha256=kiwJcVeMa4wZR-xLfM2oYOJ9DeyJkP8An38WFyJxktM,2413
@@ -95,12 +95,12 @@ trilogy/core/validation/datasource.py,sha256=nJeEFyb6iMBwlEVdYVy1vLzAbdRZwOsUjGx
95
95
  trilogy/core/validation/environment.py,sha256=ymvhQyt7jLK641JAAIQkqjQaAmr9C5022ILzYvDgPP0,2835
96
96
  trilogy/core/validation/fix.py,sha256=Z818UFNLxndMTLiyhB3doLxIfnOZ-16QGvVFWuD7UsA,3750
97
97
  trilogy/dialect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
98
- trilogy/dialect/base.py,sha256=Qk4HkjKlnAnhcZwwLte9Arb_1pVnBmkgRlwRFX1A_GQ,50680
98
+ trilogy/dialect/base.py,sha256=Eo8n5fJiHOYkfDDDFgnrqMuIZTxRorWwVer_1-DRG4c,51178
99
99
  trilogy/dialect/bigquery.py,sha256=XS3hpybeowgfrOrkycAigAF3NX2YUzTzfgE6f__2fT4,4316
100
100
  trilogy/dialect/common.py,sha256=I5Ku_TR5MwJTB3ZhcQenrtvXhH2RvTQ8wQe9w5lfkfA,5708
101
101
  trilogy/dialect/config.py,sha256=olnyeVU5W5T6b9-dMeNAnvxuPlyc2uefb7FRME094Ec,3834
102
102
  trilogy/dialect/dataframe.py,sha256=nDTHMSd7GiGjEhjAobrZND0w4zjr-vgOalM2Cdxjets,1596
103
- trilogy/dialect/duckdb.py,sha256=cRPyqnuMgjhZVaW9BYA360p-5OXle_1Xt65Yy0Vzbr4,5901
103
+ trilogy/dialect/duckdb.py,sha256=Z_mxrfQXS3KP8PEbkKsCLolyjJgPEM-aBkVCdWiLph0,6312
104
104
  trilogy/dialect/enums.py,sha256=FRNYQ5-w-B6-X0yXKNU5g9GowsMlERFogTC5u2nxL_s,4740
105
105
  trilogy/dialect/metadata.py,sha256=p_V-MYPQ2iR6fcTjagnptCIWtsZe4fTfoS_iXpavPzY,7098
106
106
  trilogy/dialect/postgres.py,sha256=el2PKwfyvWGk5EZtLudqAH5ewLitY1sFHJiocBSyxyM,3393
@@ -113,13 +113,13 @@ trilogy/hooks/graph_hook.py,sha256=5BfR7Dt0bgEsCLgwjowgCsVkboGYfVJGOz8g9mqpnos,4
113
113
  trilogy/hooks/query_debugger.py,sha256=1npRjww94sPV5RRBBlLqMJRaFkH9vhEY6o828MeoEcw,5583
114
114
  trilogy/metadata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
115
115
  trilogy/parsing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
116
- trilogy/parsing/common.py,sha256=NJLm31J3W9BLWq1ClhNvYE43jrF950698KJ3o0UfSCo,31340
116
+ trilogy/parsing/common.py,sha256=r9EmWzN2ozMKtWCY1HfjOREJnLKvaURT6h6edS5U4x8,32508
117
117
  trilogy/parsing/config.py,sha256=Z-DaefdKhPDmSXLgg5V4pebhSB0h590vI0_VtHnlukI,111
118
118
  trilogy/parsing/exceptions.py,sha256=Xwwsv2C9kSNv2q-HrrKC1f60JNHShXcCMzstTSEbiCw,154
119
119
  trilogy/parsing/helpers.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
120
- trilogy/parsing/parse_engine.py,sha256=T-3Q4UH256IB6cfX85crScZwZ6gAwslgv0fy3WKBdjc,81930
121
- trilogy/parsing/render.py,sha256=FhSU3-bMA0YM3oEn6nfpfjbM74nvH2r1TtFgbWNzOsM,24204
122
- trilogy/parsing/trilogy.lark,sha256=6eBDD6d4D9N1Nnn4CtmaoB-NpOpjHrEn5oi0JykAlbE,16509
120
+ trilogy/parsing/parse_engine.py,sha256=lMo73QD37ih5kgvAOToMLCBbCkBzo_G7QMOaQaHprV8,84333
121
+ trilogy/parsing/render.py,sha256=k7MNp8EBTqVBSVqFlgTHSwIhfSKLyJfSeb2fSbt9dVA,24307
122
+ trilogy/parsing/trilogy.lark,sha256=kPOIrwa__kdXvxS3uM5GzdlgIa2cQxNW_0DNRqsRBeg,16939
123
123
  trilogy/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
124
124
  trilogy/scripts/trilogy.py,sha256=1L0XrH4mVHRt1C9T1HnaDv2_kYEfbWTb5_-cBBke79w,3774
125
125
  trilogy/std/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -132,8 +132,8 @@ trilogy/std/money.preql,sha256=XWwvAV3WxBsHX9zfptoYRnBigcfYwrYtBHXTME0xJuQ,2082
132
132
  trilogy/std/net.preql,sha256=WZCuvH87_rZntZiuGJMmBDMVKkdhTtxeHOkrXNwJ1EE,416
133
133
  trilogy/std/ranking.preql,sha256=LDoZrYyz4g3xsII9XwXfmstZD-_92i1Eox1UqkBIfi8,83
134
134
  trilogy/std/report.preql,sha256=LbV-XlHdfw0jgnQ8pV7acG95xrd1-p65fVpiIc-S7W4,202
135
- pytrilogy-0.0.3.112.dist-info/METADATA,sha256=vUL4xdKsovl8VHq7P_NyBTDiI31R0E2EkcJYOog7A8Q,13289
136
- pytrilogy-0.0.3.112.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
137
- pytrilogy-0.0.3.112.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
138
- pytrilogy-0.0.3.112.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
139
- pytrilogy-0.0.3.112.dist-info/RECORD,,
135
+ pytrilogy-0.0.3.115.dist-info/METADATA,sha256=pB3OdY3t5s8GkX73rniQKQkMSy8JcXjzKZ0w1va3k5E,12911
136
+ pytrilogy-0.0.3.115.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
137
+ pytrilogy-0.0.3.115.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
138
+ pytrilogy-0.0.3.115.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
139
+ pytrilogy-0.0.3.115.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.112"
7
+ __version__ = "0.0.3.115"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
trilogy/constants.py CHANGED
@@ -1,7 +1,9 @@
1
1
  import random
2
+ from contextlib import contextmanager
2
3
  from dataclasses import dataclass, field
3
4
  from enum import Enum
4
5
  from logging import getLogger
6
+ from typing import Any
5
7
 
6
8
  logger = getLogger("trilogy")
7
9
 
@@ -50,6 +52,32 @@ class Rendering:
50
52
  parameters: bool = True
51
53
  concise: bool = False
52
54
 
55
+ @contextmanager
56
+ def temporary(self, **kwargs: Any):
57
+ """
58
+ Context manager to temporarily set attributes and revert them afterwards.
59
+
60
+ Usage:
61
+ r = Rendering()
62
+ with r.temporary(parameters=False, concise=True):
63
+ # parameters is False, concise is True here
64
+ do_something()
65
+ # parameters and concise are back to their original values
66
+ """
67
+ # Store original values
68
+ original_values = {key: getattr(self, key) for key in kwargs}
69
+
70
+ # Set new values
71
+ for key, value in kwargs.items():
72
+ setattr(self, key, value)
73
+
74
+ try:
75
+ yield self
76
+ finally:
77
+ # Restore original values
78
+ for key, value in original_values.items():
79
+ setattr(self, key, value)
80
+
53
81
 
54
82
  @dataclass
55
83
  class Parsing:
trilogy/core/enums.py CHANGED
@@ -169,6 +169,7 @@ class FunctionType(Enum):
169
169
  ARRAY_SORT = "array_sort"
170
170
  ARRAY_TRANSFORM = "array_transform"
171
171
  ARRAY_TO_STRING = "array_to_string"
172
+ ARRAY_FILTER = "array_filter"
172
173
 
173
174
  # MAP
174
175
  MAP_KEYS = "map_keys"
@@ -204,6 +205,7 @@ class FunctionType(Enum):
204
205
  MIN = "min"
205
206
  AVG = "avg"
206
207
  ARRAY_AGG = "array_agg"
208
+ ANY = "any"
207
209
 
208
210
  # String
209
211
  LIKE = "like"
@@ -236,12 +238,15 @@ class FunctionType(Enum):
236
238
  MONTH = "month"
237
239
  QUARTER = "quarter"
238
240
  YEAR = "year"
241
+ MONTH_NAME = "month_name"
242
+ DAY_NAME = "day_name"
239
243
 
240
244
  DATE_PART = "date_part"
241
245
  DATE_TRUNCATE = "date_truncate"
242
246
  DATE_ADD = "date_add"
243
247
  DATE_SUB = "date_sub"
244
248
  DATE_DIFF = "date_diff"
249
+ DATE_SPINE = "date_spine"
245
250
 
246
251
  # UNIX
247
252
  UNIX_TO_TIMESTAMP = "unix_to_timestamp"
@@ -261,6 +266,7 @@ class FunctionClass(Enum):
261
266
  FunctionType.ARRAY_AGG,
262
267
  FunctionType.COUNT,
263
268
  FunctionType.COUNT_DISTINCT,
269
+ FunctionType.ANY,
264
270
  ]
265
271
  SINGLE_ROW = [
266
272
  FunctionType.CONSTANT,
@@ -268,7 +274,7 @@ class FunctionClass(Enum):
268
274
  FunctionType.CURRENT_DATETIME,
269
275
  ]
270
276
 
271
- ONE_TO_MANY = [FunctionType.UNNEST]
277
+ ONE_TO_MANY = [FunctionType.UNNEST, FunctionType.DATE_SPINE]
272
278
 
273
279
  RECURSIVE = [FunctionType.RECURSE_EDGE]
274
280
 
trilogy/core/functions.py CHANGED
@@ -175,6 +175,10 @@ def get_date_trunc_output(
175
175
  return DataType.DATETIME
176
176
  elif target == DatePart.SECOND:
177
177
  return DataType.DATETIME
178
+ elif target == DatePart.WEEK:
179
+ return DataType.DATE
180
+ elif target == DatePart.QUARTER:
181
+ return DataType.DATE
178
182
  else:
179
183
  raise InvalidSyntaxException(f"Date truncation not supported for {target}")
180
184
 
@@ -208,6 +212,14 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
208
212
  output_type_function=get_unnest_output_type,
209
213
  arg_count=1,
210
214
  ),
215
+ FunctionType.DATE_SPINE: FunctionConfig(
216
+ valid_inputs={
217
+ DataType.DATE,
218
+ },
219
+ output_purpose=Purpose.KEY,
220
+ output_type=DataType.DATE,
221
+ arg_count=2,
222
+ ),
211
223
  FunctionType.RECURSE_EDGE: FunctionConfig(
212
224
  arg_count=2,
213
225
  ),
@@ -314,6 +326,18 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
314
326
  output_type_function=get_transform_output_type,
315
327
  arg_count=3,
316
328
  ),
329
+ FunctionType.ARRAY_FILTER: FunctionConfig(
330
+ valid_inputs=[
331
+ {
332
+ DataType.ARRAY,
333
+ },
334
+ {*DataType},
335
+ {*DataType},
336
+ ],
337
+ output_purpose=Purpose.PROPERTY,
338
+ output_type_function=get_transform_output_type,
339
+ arg_count=3,
340
+ ),
317
341
  FunctionType.ARRAY_TO_STRING: FunctionConfig(
318
342
  valid_inputs={
319
343
  DataType.ARRAY,
@@ -640,6 +664,17 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
640
664
  output_type=TraitDataType(type=DataType.INTEGER, traits=["day"]),
641
665
  arg_count=1,
642
666
  ),
667
+ FunctionType.DAY_NAME: FunctionConfig(
668
+ valid_inputs={
669
+ DataType.DATE,
670
+ DataType.TIMESTAMP,
671
+ DataType.DATETIME,
672
+ # DataType.STRING,
673
+ },
674
+ output_purpose=Purpose.PROPERTY,
675
+ output_type=TraitDataType(type=DataType.STRING, traits=["day_name"]),
676
+ arg_count=1,
677
+ ),
643
678
  FunctionType.WEEK: FunctionConfig(
644
679
  valid_inputs={
645
680
  DataType.DATE,
@@ -662,6 +697,17 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
662
697
  output_type=TraitDataType(type=DataType.INTEGER, traits=["month"]),
663
698
  arg_count=1,
664
699
  ),
700
+ FunctionType.MONTH_NAME: FunctionConfig(
701
+ valid_inputs={
702
+ DataType.DATE,
703
+ DataType.TIMESTAMP,
704
+ DataType.DATETIME,
705
+ # DataType.STRING,
706
+ },
707
+ output_purpose=Purpose.PROPERTY,
708
+ output_type=TraitDataType(type=DataType.STRING, traits=["month_name"]),
709
+ arg_count=1,
710
+ ),
665
711
  FunctionType.QUARTER: FunctionConfig(
666
712
  valid_inputs={
667
713
  DataType.DATE,
@@ -861,6 +907,11 @@ FUNCTION_REGISTRY: dict[FunctionType, FunctionConfig] = {
861
907
  ),
862
908
  arg_count=1,
863
909
  ),
910
+ FunctionType.ANY: FunctionConfig(
911
+ valid_inputs={*DataType},
912
+ output_purpose=Purpose.PROPERTY,
913
+ arg_count=1,
914
+ ),
864
915
  FunctionType.AVG: FunctionConfig(
865
916
  valid_inputs={
866
917
  DataType.INTEGER,
@@ -461,6 +461,11 @@ class WhereClause(Mergeable, ConceptArgs, Namespaced, BaseModel):
461
461
  conditional=self.conditional.with_namespace(namespace)
462
462
  )
463
463
 
464
+ def with_reference_replacement(self, source, target):
465
+ return self.__class__.model_construct(
466
+ conditional=self.conditional.with_reference_replacement(source, target)
467
+ )
468
+
464
469
 
465
470
  class HavingClause(WhereClause):
466
471
  pass
@@ -1212,6 +1217,8 @@ class Concept(Addressable, DataTyped, ConceptArgs, Mergeable, Namespaced, BaseMo
1212
1217
  return Derivation.FILTER
1213
1218
  elif lineage and isinstance(lineage, (BuildAggregateWrapper, AggregateWrapper)):
1214
1219
  return Derivation.AGGREGATE
1220
+ # elif lineage and isinstance(lineage, (BuildParenthetical, Parenthetical)):
1221
+ # return Derivation.PARENTHETICAL
1215
1222
  elif lineage and isinstance(lineage, (BuildRowsetItem, RowsetItem)):
1216
1223
  return Derivation.ROWSET
1217
1224
  elif lineage and isinstance(
@@ -1227,7 +1234,7 @@ class Concept(Addressable, DataTyped, ConceptArgs, Mergeable, Namespaced, BaseMo
1227
1234
  elif (
1228
1235
  lineage
1229
1236
  and isinstance(lineage, (BuildFunction, Function))
1230
- and lineage.operator == FunctionType.UNNEST
1237
+ and lineage.operator in FunctionClass.ONE_TO_MANY.value
1231
1238
  ):
1232
1239
  return Derivation.UNNEST
1233
1240
  elif (
@@ -1279,7 +1286,8 @@ class Concept(Addressable, DataTyped, ConceptArgs, Mergeable, Namespaced, BaseMo
1279
1286
  elif (
1280
1287
  lineage
1281
1288
  and isinstance(lineage, (Function, BuildFunction))
1282
- and lineage.operator in (FunctionType.UNNEST, FunctionType.UNION)
1289
+ and lineage.operator
1290
+ in (FunctionType.UNNEST, FunctionType.UNION, FunctionType.DATE_SPINE)
1283
1291
  ):
1284
1292
  return Granularity.MULTI_ROW
1285
1293
  elif lineage and all(
@@ -134,8 +134,9 @@ def concept_is_relevant(
134
134
  if concept.purpose in (Purpose.METRIC,):
135
135
  if all([c in others for c in concept.grain.components]):
136
136
  return False
137
+ if concept.derivation in (Derivation.UNNEST,):
138
+ return True
137
139
  if concept.derivation in (Derivation.BASIC,):
138
-
139
140
  return any(concept_is_relevant(c, others) for c in concept.concept_arguments)
140
141
  if concept.granularity == Granularity.SINGLE_ROW:
141
142
  return False
@@ -1668,7 +1669,6 @@ class Factory:
1668
1669
  valid_inputs=base.valid_inputs,
1669
1670
  arg_count=base.arg_count,
1670
1671
  )
1671
-
1672
1672
  new = BuildFunction(
1673
1673
  operator=base.operator,
1674
1674
  arguments=[self.handle_constant(self.build(c)) for c in raw_args],
@@ -1724,6 +1724,14 @@ class Factory:
1724
1724
  return self._build_concept(base)
1725
1725
 
1726
1726
  def _build_concept(self, base: Concept) -> BuildConcept:
1727
+ try:
1728
+ return self.__build_concept(base)
1729
+ except RecursionError as e:
1730
+ raise RecursionError(
1731
+ f"Recursion error building concept {base.address}. This is likely due to a circular reference."
1732
+ ) from e
1733
+
1734
+ def __build_concept(self, base: Concept) -> BuildConcept:
1727
1735
  # TODO: if we are using parameters, wrap it in a new model and use that in rendering
1728
1736
  if base.address in self.local_concepts:
1729
1737
  return self.local_concepts[base.address]
@@ -2002,6 +2010,13 @@ class Factory:
2002
2010
  def _build_tuple_wrapper(self, base: TupleWrapper) -> TupleWrapper:
2003
2011
  return TupleWrapper(val=[self.build(x) for x in base.val], type=base.type)
2004
2012
 
2013
+ @build.register
2014
+ def _(self, base: ListWrapper) -> ListWrapper:
2015
+ return self._build_list_wrapper(base)
2016
+
2017
+ def _build_list_wrapper(self, base: ListWrapper) -> ListWrapper:
2018
+ return ListWrapper([self.build(x) for x in base], type=base.type)
2019
+
2005
2020
  @build.register
2006
2021
  def _(self, base: FilterItem) -> BuildFilterItem:
2007
2022
  return self._build_filter_item(base)
@@ -563,6 +563,7 @@ class Environment(BaseModel):
563
563
  existing = self.validate_concept(concept, meta=meta)
564
564
  if existing:
565
565
  concept = existing
566
+
566
567
  self.concepts[concept.address] = concept
567
568
 
568
569
  from trilogy.core.environment_helpers import generate_related_concepts
@@ -306,7 +306,12 @@ def evaluate_loop_conditions(
306
306
 
307
307
 
308
308
  def check_for_early_exit(
309
- complete, partial, missing, context: LoopContext, priority_concept: BuildConcept
309
+ complete: ValidationResult,
310
+ found: set[str],
311
+ partial: set[str],
312
+ missing: set[str],
313
+ context: LoopContext,
314
+ priority_concept: BuildConcept,
310
315
  ) -> bool:
311
316
  if complete == ValidationResult.INCOMPLETE_CONDITION:
312
317
  cond_dict = {str(node): node.preexisting_conditions for node in context.stack}
@@ -331,8 +336,18 @@ def check_for_early_exit(
331
336
  f"{depth_to_prefix(context.depth)}{LOGGER_PREFIX} Breaking as we have attempted all nodes"
332
337
  )
333
338
  return True
339
+ elif all(
340
+ [
341
+ x.address in found and x.address not in partial
342
+ for x in context.mandatory_list
343
+ ]
344
+ ):
345
+ logger.info(
346
+ f"{depth_to_prefix(context.depth)}{LOGGER_PREFIX} Breaking as we have found all mandatory nodes without partials"
347
+ )
348
+ return True
334
349
  logger.info(
335
- f"{depth_to_prefix(context.depth)}{LOGGER_PREFIX} Found complete stack with partials {partial}, continuing search, attempted {context.attempted} all {len(context.mandatory_list)}"
350
+ f"{depth_to_prefix(context.depth)}{LOGGER_PREFIX} Found complete stack with partials {partial}, continuing search, attempted {context.attempted} of total {len(context.mandatory_list)}."
336
351
  )
337
352
  else:
338
353
  logger.info(
@@ -436,6 +451,7 @@ def generate_loop_completion(context: LoopContext, virtual: set[str]) -> Strateg
436
451
  context.original_mandatory,
437
452
  context.environment,
438
453
  non_virtual_difference_values,
454
+ depth=context.depth,
439
455
  )
440
456
 
441
457
  return group_if_required_v2(
@@ -443,6 +459,7 @@ def generate_loop_completion(context: LoopContext, virtual: set[str]) -> Strateg
443
459
  context.original_mandatory,
444
460
  context.environment,
445
461
  non_virtual_difference_values,
462
+ depth=context.depth,
446
463
  )
447
464
 
448
465
 
@@ -466,6 +483,7 @@ def _search_concepts(
466
483
  conditions=conditions,
467
484
  )
468
485
 
486
+ # if we get a can
469
487
  if candidate:
470
488
  return candidate
471
489
  context = initialize_loop_context(
@@ -477,13 +495,14 @@ def _search_concepts(
477
495
  accept_partial=accept_partial,
478
496
  conditions=conditions,
479
497
  )
480
-
498
+ partial: set[str] = set()
481
499
  while context.incomplete:
482
500
 
483
501
  priority_concept = get_priority_concept(
484
502
  context.mandatory_list,
485
503
  context.attempted,
486
504
  found_concepts=context.found,
505
+ partial_concepts=partial,
487
506
  depth=depth,
488
507
  )
489
508
 
@@ -538,7 +557,7 @@ def _search_concepts(
538
557
  # assign
539
558
  context.found = found_c
540
559
  early_exit = check_for_early_exit(
541
- complete, partial, missing_c, context, priority_concept
560
+ complete, found_c, partial, missing_c, context, priority_concept
542
561
  )
543
562
  if early_exit:
544
563
  break
@@ -608,4 +627,4 @@ def source_query_concepts(
608
627
  logger.info(
609
628
  f"{depth_to_prefix(0)}{LOGGER_PREFIX} final concepts are {[x.address for x in final]}"
610
629
  )
611
- return group_if_required_v2(root, output_concepts, environment)
630
+ return group_if_required_v2(root, output_concepts, environment, depth=0)
@@ -187,7 +187,7 @@ def _generate_aggregate_node(ctx: NodeGenerationContext) -> StrategyNode | None:
187
187
 
188
188
  logger.info(
189
189
  f"{depth_to_prefix(ctx.depth)}{LOGGER_PREFIX} "
190
- f"for {ctx.concept.address}, generating aggregate node with {agg_optional}"
190
+ f"for {ctx.concept.address}, generating aggregate node with optional {agg_optional}"
191
191
  )
192
192
 
193
193
  return gen_group_node(
@@ -441,7 +441,7 @@ def generate_node(
441
441
  depth: int,
442
442
  source_concepts: SearchConceptsType,
443
443
  history: History,
444
- accept_partial: bool = False,
444
+ accept_partial: bool,
445
445
  conditions: BuildWhereClause | None = None,
446
446
  ) -> StrategyNode | None:
447
447
 
@@ -184,10 +184,14 @@ def group_if_required_v2(
184
184
  final: List[BuildConcept],
185
185
  environment: BuildEnvironment,
186
186
  where_injected: set[str] | None = None,
187
+ depth: int = 0,
187
188
  ):
188
189
  where_injected = where_injected or set()
189
190
  required = check_if_group_required(
190
- downstream_concepts=final, parents=[root.resolve()], environment=environment
191
+ downstream_concepts=final,
192
+ parents=[root.resolve()],
193
+ environment=environment,
194
+ depth=depth,
191
195
  )
192
196
  targets = [
193
197
  x
@@ -258,6 +262,7 @@ def get_priority_concept(
258
262
  all_concepts: List[BuildConcept],
259
263
  attempted_addresses: set[str],
260
264
  found_concepts: set[str],
265
+ partial_concepts: set[str],
261
266
  depth: int,
262
267
  ) -> BuildConcept:
263
268
  # optimized search for missing concepts
@@ -265,13 +270,15 @@ def get_priority_concept(
265
270
  [
266
271
  c
267
272
  for c in all_concepts
268
- if c.address not in attempted_addresses and c.address not in found_concepts
273
+ if c.address not in attempted_addresses
274
+ and (c.address not in found_concepts or c.address in partial_concepts)
269
275
  ],
270
276
  key=lambda x: x.address,
271
277
  )
272
278
  # sometimes we need to scan intermediate concepts to get merge keys or filter keys,
273
279
  # so do an exhaustive search
274
- # pass_two = [c for c in all_concepts+filter_only if c.address not in attempted_addresses]
280
+ # pass_two = [c for c in all_concepts if c.address not in attempted_addresses]
281
+
275
282
  for remaining_concept in (pass_one,):
276
283
  priority = (
277
284
  # then multiselects to remove them from scope
@@ -333,5 +340,5 @@ def get_priority_concept(
333
340
  if final:
334
341
  return final[0]
335
342
  raise ValueError(
336
- f"Cannot resolve query. No remaining priority concepts, have attempted {attempted_addresses}"
343
+ f"Cannot resolve query. No remaining priority concepts, have attempted {attempted_addresses} out of {all_concepts} with found {found_concepts}"
337
344
  )