pylegend 0.3.0__py3-none-any.whl → 0.5.0__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.
Files changed (124) hide show
  1. pylegend/__init__.py +16 -6
  2. pylegend/core/{databse → database}/sql_to_string/__init__.py +3 -3
  3. pylegend/core/{databse → database}/sql_to_string/db_extension.py +14 -5
  4. pylegend/core/{databse → database}/sql_to_string/generator.py +2 -2
  5. pylegend/core/language/__init__.py +12 -10
  6. pylegend/core/language/legacy_api/__init__.py +13 -0
  7. pylegend/core/language/{aggregate_specification.py → legacy_api/aggregate_specification.py} +10 -10
  8. pylegend/core/language/legacy_api/legacy_api_tds_row.py +32 -0
  9. pylegend/core/language/legendql_api/__init__.py +13 -0
  10. pylegend/core/language/legendql_api/legendql_api_custom_expressions.py +541 -0
  11. pylegend/core/language/legendql_api/legendql_api_tds_row.py +292 -0
  12. pylegend/core/language/shared/__init__.py +13 -0
  13. pylegend/core/language/{column_expressions.py → shared/column_expressions.py} +32 -31
  14. pylegend/core/language/{expression.py → shared/expression.py} +8 -0
  15. pylegend/core/language/{functions.py → shared/functions.py} +12 -3
  16. pylegend/core/language/shared/helpers.py +75 -0
  17. pylegend/core/language/{literal_expressions.py → shared/literal_expressions.py} +39 -1
  18. pylegend/core/language/{operations → shared/operations}/binary_expression.py +34 -2
  19. pylegend/core/language/{operations → shared/operations}/boolean_operation_expressions.py +34 -6
  20. pylegend/core/language/{operations → shared/operations}/collection_operation_expressions.py +146 -26
  21. pylegend/core/language/{operations → shared/operations}/date_operation_expressions.py +288 -24
  22. pylegend/core/language/{operations → shared/operations}/float_operation_expressions.py +53 -8
  23. pylegend/core/language/{operations → shared/operations}/integer_operation_expressions.py +62 -9
  24. pylegend/core/language/{operations → shared/operations}/nullary_expression.py +9 -2
  25. pylegend/core/language/{operations → shared/operations}/number_operation_expressions.py +211 -30
  26. pylegend/core/language/shared/operations/primitive_operation_expressions.py +155 -0
  27. pylegend/core/language/{operations → shared/operations}/string_operation_expressions.py +194 -21
  28. pylegend/core/language/{operations → shared/operations}/unary_expression.py +10 -2
  29. pylegend/core/language/{primitive_collection.py → shared/primitive_collection.py} +2 -2
  30. pylegend/core/language/{primitives → shared/primitives}/__init__.py +9 -9
  31. pylegend/core/language/{primitives → shared/primitives}/boolean.py +9 -5
  32. pylegend/core/language/{primitives → shared/primitives}/date.py +60 -15
  33. pylegend/core/language/{primitives → shared/primitives}/datetime.py +4 -5
  34. pylegend/core/language/{primitives → shared/primitives}/float.py +6 -6
  35. pylegend/core/language/{primitives → shared/primitives}/integer.py +6 -6
  36. pylegend/core/language/{primitives → shared/primitives}/number.py +16 -13
  37. pylegend/core/language/{primitives → shared/primitives}/primitive.py +41 -5
  38. pylegend/core/language/{primitives → shared/primitives}/strictdate.py +4 -5
  39. pylegend/core/language/{primitives → shared/primitives}/string.py +18 -19
  40. pylegend/core/language/{tds_row.py → shared/tds_row.py} +46 -16
  41. pylegend/core/request/__init__.py +7 -1
  42. pylegend/core/request/auth.py +55 -1
  43. pylegend/core/request/legend_client.py +32 -0
  44. pylegend/core/sql/metamodel_extension.py +28 -0
  45. pylegend/core/tds/abstract/__init__.py +13 -0
  46. pylegend/core/tds/abstract/frames/__init__.py +13 -0
  47. pylegend/core/tds/{legend_api/frames/legend_api_applied_function_tds_frame.py → abstract/frames/applied_function_tds_frame.py} +19 -13
  48. pylegend/core/tds/abstract/frames/base_tds_frame.py +125 -0
  49. pylegend/core/tds/{legend_api/frames/legend_api_input_tds_frame.py → abstract/frames/input_tds_frame.py} +9 -12
  50. pylegend/core/tds/{legend_api/frames/functions → abstract}/function_helpers.py +1 -1
  51. pylegend/core/tds/{legend_api/frames/functions/concatenate_function.py → legacy_api/frames/functions/legacy_api_concatenate_function.py} +25 -13
  52. pylegend/core/tds/{legend_api/frames/functions/distinct_function.py → legacy_api/frames/functions/legacy_api_distinct_function.py} +13 -8
  53. pylegend/core/tds/{legend_api/frames/functions/drop_function.py → legacy_api/frames/functions/legacy_api_drop_function.py} +13 -8
  54. pylegend/core/tds/{legend_api/frames/functions/extend_function.py → legacy_api/frames/functions/legacy_api_extend_function.py} +36 -16
  55. pylegend/core/tds/{legend_api/frames/functions/filter_function.py → legacy_api/frames/functions/legacy_api_filter_function.py} +25 -13
  56. pylegend/core/tds/{legend_api/frames/functions/group_by_function.py → legacy_api/frames/functions/legacy_api_group_by_function.py} +44 -17
  57. pylegend/core/tds/{legend_api/frames/functions/head_function.py → legacy_api/frames/functions/legacy_api_head_function.py} +13 -8
  58. pylegend/core/tds/{legend_api/frames/functions/join_by_columns_function.py → legacy_api/frames/functions/legacy_api_join_by_columns_function.py} +40 -13
  59. pylegend/core/tds/{legend_api/frames/functions/join_function.py → legacy_api/frames/functions/legacy_api_join_function.py} +44 -20
  60. pylegend/core/tds/{legend_api/frames/functions/rename_columns_function.py → legacy_api/frames/functions/legacy_api_rename_columns_function.py} +20 -8
  61. pylegend/core/tds/{legend_api/frames/functions/restrict_function.py → legacy_api/frames/functions/legacy_api_restrict_function.py} +17 -8
  62. pylegend/core/tds/{legend_api/frames/functions/slice_function.py → legacy_api/frames/functions/legacy_api_slice_function.py} +13 -8
  63. pylegend/core/tds/{legend_api/frames/functions/sort_function.py → legacy_api/frames/functions/legacy_api_sort_function.py} +19 -8
  64. pylegend/core/tds/legacy_api/frames/legacy_api_applied_function_tds_frame.py +37 -0
  65. pylegend/core/tds/legacy_api/frames/legacy_api_base_tds_frame.py +204 -0
  66. pylegend/core/tds/legacy_api/frames/legacy_api_input_tds_frame.py +51 -0
  67. pylegend/core/tds/{legend_api/frames/legend_api_tds_frame.py → legacy_api/frames/legacy_api_tds_frame.py} +28 -28
  68. pylegend/core/tds/legendql_api/__init__.py +13 -0
  69. pylegend/core/tds/legendql_api/frames/__init__.py +13 -0
  70. pylegend/core/tds/legendql_api/frames/functions/__init__.py +13 -0
  71. pylegend/core/tds/legendql_api/frames/functions/legendql_api_asofjoin_function.py +156 -0
  72. pylegend/core/tds/legendql_api/frames/functions/legendql_api_concatenate_function.py +139 -0
  73. pylegend/core/tds/legendql_api/frames/functions/legendql_api_distinct_function.py +69 -0
  74. pylegend/core/tds/legendql_api/frames/functions/legendql_api_drop_function.py +74 -0
  75. pylegend/core/tds/legendql_api/frames/functions/legendql_api_extend_function.py +256 -0
  76. pylegend/core/tds/legendql_api/frames/functions/legendql_api_filter_function.py +121 -0
  77. pylegend/core/tds/legendql_api/frames/functions/legendql_api_function_helpers.py +137 -0
  78. pylegend/core/tds/legendql_api/frames/functions/legendql_api_groupby_function.py +256 -0
  79. pylegend/core/tds/legendql_api/frames/functions/legendql_api_head_function.py +74 -0
  80. pylegend/core/tds/legendql_api/frames/functions/legendql_api_join_function.py +214 -0
  81. pylegend/core/tds/legendql_api/frames/functions/legendql_api_project_function.py +169 -0
  82. pylegend/core/tds/legendql_api/frames/functions/legendql_api_rename_function.py +189 -0
  83. pylegend/core/tds/legendql_api/frames/functions/legendql_api_select_function.py +131 -0
  84. pylegend/core/tds/legendql_api/frames/functions/legendql_api_slice_function.py +82 -0
  85. pylegend/core/tds/legendql_api/frames/functions/legendql_api_sort_function.py +93 -0
  86. pylegend/core/tds/legendql_api/frames/functions/legendql_api_window_extend_function.py +283 -0
  87. pylegend/core/tds/legendql_api/frames/legendql_api_applied_function_tds_frame.py +37 -0
  88. pylegend/core/tds/legendql_api/frames/legendql_api_base_tds_frame.py +419 -0
  89. pylegend/core/tds/legendql_api/frames/legendql_api_input_tds_frame.py +50 -0
  90. pylegend/core/tds/legendql_api/frames/legendql_api_tds_frame.py +327 -0
  91. pylegend/core/tds/pandas_api/frames/functions/assign_function.py +6 -6
  92. pylegend/core/tds/pandas_api/frames/pandas_api_applied_function_tds_frame.py +4 -0
  93. pylegend/core/tds/pandas_api/frames/pandas_api_base_tds_frame.py +11 -3
  94. pylegend/core/tds/pandas_api/frames/pandas_api_tds_frame.py +2 -2
  95. pylegend/core/tds/tds_frame.py +32 -2
  96. pylegend/extensions/database/vendors/postgres/postgres_sql_to_string.py +1 -1
  97. pylegend/extensions/tds/abstract/legend_function_input_frame.py +4 -0
  98. pylegend/extensions/tds/abstract/legend_service_input_frame.py +4 -0
  99. pylegend/extensions/tds/abstract/table_spec_input_frame.py +4 -0
  100. pylegend/extensions/tds/{legend_api/frames/legend_api_legend_function_input_frame.py → legacy_api/frames/legacy_api_legend_function_input_frame.py} +5 -5
  101. pylegend/extensions/tds/{legend_api/frames/legend_api_legend_service_input_frame.py → legacy_api/frames/legacy_api_legend_service_input_frame.py} +6 -6
  102. pylegend/extensions/tds/{legend_api/frames/legend_api_table_spec_input_frame.py → legacy_api/frames/legacy_api_table_spec_input_frame.py} +5 -5
  103. pylegend/extensions/tds/legendql_api/__init__.py +13 -0
  104. pylegend/extensions/tds/legendql_api/frames/__init__.py +13 -0
  105. pylegend/extensions/tds/legendql_api/frames/legendql_api_legend_service_input_frame.py +46 -0
  106. pylegend/extensions/tds/legendql_api/frames/legendql_api_table_spec_input_frame.py +36 -0
  107. pylegend/{legend_api_tds_client.py → legacy_api_tds_client.py} +15 -15
  108. {pylegend-0.3.0.dist-info → pylegend-0.5.0.dist-info}/METADATA +7 -8
  109. pylegend-0.5.0.dist-info/NOTICE +5 -0
  110. pylegend-0.5.0.dist-info/RECORD +155 -0
  111. {pylegend-0.3.0.dist-info → pylegend-0.5.0.dist-info}/WHEEL +1 -1
  112. pylegend/core/language/operations/primitive_operation_expressions.py +0 -56
  113. pylegend/core/tds/legend_api/frames/legend_api_base_tds_frame.py +0 -294
  114. pylegend-0.3.0.dist-info/RECORD +0 -115
  115. /pylegend/core/{databse → database}/__init__.py +0 -0
  116. /pylegend/core/{databse → database}/sql_to_string/config.py +0 -0
  117. /pylegend/core/language/{operations → shared/operations}/__init__.py +0 -0
  118. /pylegend/core/tds/{legend_api → legacy_api}/__init__.py +0 -0
  119. /pylegend/core/tds/{legend_api → legacy_api}/frames/__init__.py +0 -0
  120. /pylegend/core/tds/{legend_api → legacy_api}/frames/functions/__init__.py +0 -0
  121. /pylegend/extensions/tds/{legend_api → legacy_api}/__init__.py +0 -0
  122. /pylegend/extensions/tds/{legend_api → legacy_api}/frames/__init__.py +0 -0
  123. {pylegend-0.3.0.dist-info → pylegend-0.5.0.dist-info}/LICENSE +0 -0
  124. {pylegend-0.3.0.dist-info → pylegend-0.5.0.dist-info}/LICENSE.spdx +0 -0
@@ -0,0 +1,292 @@
1
+ # Copyright 2025 Goldman Sachs
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from pylegend._typing import (
16
+ PyLegendSequence,
17
+ PyLegendDict,
18
+ )
19
+ from pylegend.core.language import (
20
+ PyLegendBoolean,
21
+ PyLegendString,
22
+ PyLegendInteger,
23
+ PyLegendFloat,
24
+ PyLegendNumber,
25
+ PyLegendStrictDate,
26
+ PyLegendDateTime,
27
+ PyLegendDate,
28
+ )
29
+ from pylegend.core.language.legendql_api.legendql_api_custom_expressions import (
30
+ LegendQLApiBoolean,
31
+ LegendQLApiString,
32
+ LegendQLApiInteger,
33
+ LegendQLApiFloat,
34
+ LegendQLApiNumber,
35
+ LegendQLApiStrictDate,
36
+ LegendQLApiDateTime,
37
+ LegendQLApiDate,
38
+ LegendQLApiPrimitive,
39
+ LegendQLApiPartialFrame,
40
+ LegendQLApiWindowReference,
41
+ )
42
+ from pylegend.core.language.shared.tds_row import AbstractTdsRow
43
+ from pylegend.core.sql.metamodel import (
44
+ QuerySpecification,
45
+ Expression,
46
+ FunctionCall,
47
+ QualifiedName,
48
+ IntegerLiteral
49
+ )
50
+ from pylegend.core.tds.tds_frame import PyLegendTdsFrame, FrameToPureConfig, FrameToSqlConfig
51
+
52
+ __all__: PyLegendSequence[str] = [
53
+ "LegendQLApiTdsRow",
54
+ "LegendQLApiLeadRow",
55
+ "LegendQLApiLagRow",
56
+ "LegendQLApiFirstRow",
57
+ "LegendQLApiLastRow",
58
+ "LegendQLApiNthRow",
59
+ ]
60
+
61
+
62
+ class LegendQLApiTdsRow(AbstractTdsRow):
63
+
64
+ def __init__(self, frame_name: str, frame: PyLegendTdsFrame) -> None:
65
+ super().__init__(frame_name, frame)
66
+
67
+ @staticmethod
68
+ def from_tds_frame(frame_name: str, frame: PyLegendTdsFrame) -> "LegendQLApiTdsRow":
69
+ return LegendQLApiTdsRow(frame_name=frame_name, frame=frame)
70
+
71
+ def __getattr__(self, key: str) -> LegendQLApiPrimitive:
72
+ return self[key]
73
+
74
+ def get_boolean(self, column: str) -> LegendQLApiBoolean:
75
+ return LegendQLApiBoolean(super().get_boolean(column))
76
+
77
+ def get_string(self, column: str) -> LegendQLApiString:
78
+ return LegendQLApiString(super().get_string(column))
79
+
80
+ def get_number(self, column: str) -> LegendQLApiNumber:
81
+ return LegendQLApiNumber(super().get_number(column))
82
+
83
+ def get_integer(self, column: str) -> LegendQLApiInteger:
84
+ return LegendQLApiInteger(super().get_integer(column))
85
+
86
+ def get_float(self, column: str) -> LegendQLApiFloat:
87
+ return LegendQLApiFloat(super().get_float(column))
88
+
89
+ def get_date(self, column: str) -> LegendQLApiDate:
90
+ return LegendQLApiDate(super().get_date(column))
91
+
92
+ def get_datetime(self, column: str) -> LegendQLApiDateTime:
93
+ return LegendQLApiDateTime(super().get_datetime(column))
94
+
95
+ def get_strictdate(self, column: str) -> LegendQLApiStrictDate:
96
+ return LegendQLApiStrictDate(super().get_strictdate(column))
97
+
98
+ def __getitem__(self, item: str) -> LegendQLApiPrimitive:
99
+ res = super().__getitem__(item)
100
+ if isinstance(res, PyLegendBoolean):
101
+ return LegendQLApiBoolean(res)
102
+ if isinstance(res, PyLegendString):
103
+ return LegendQLApiString(res)
104
+ if isinstance(res, PyLegendInteger):
105
+ return LegendQLApiInteger(res)
106
+ if isinstance(res, PyLegendFloat):
107
+ return LegendQLApiFloat(res)
108
+ if isinstance(res, PyLegendNumber):
109
+ return LegendQLApiNumber(res)
110
+ if isinstance(res, PyLegendStrictDate):
111
+ return LegendQLApiStrictDate(res)
112
+ if isinstance(res, PyLegendDateTime):
113
+ return LegendQLApiDateTime(res)
114
+ if isinstance(res, PyLegendDate):
115
+ return LegendQLApiDate(res)
116
+
117
+ raise RuntimeError(f"Unhandled primitive type {type(res)} in LegendQL Api")
118
+
119
+
120
+ class LegendQLApiLeadRow(LegendQLApiTdsRow):
121
+ __partial_frame: LegendQLApiPartialFrame
122
+ __row: "LegendQLApiTdsRow"
123
+
124
+ def __init__(
125
+ self,
126
+ partial_frame: LegendQLApiPartialFrame,
127
+ row: "LegendQLApiTdsRow"
128
+ ) -> None:
129
+ super().__init__(frame_name=row.get_frame_name(), frame=partial_frame.get_base_frame())
130
+ self.__partial_frame = partial_frame
131
+ self.__row = row
132
+
133
+ def to_pure_expression(self, config: FrameToPureConfig) -> str:
134
+ return f"{self.__partial_frame.to_pure_expression(config)}->lead({self.__row.to_pure_expression(config)})"
135
+
136
+ def column_sql_expression(
137
+ self,
138
+ column: str,
139
+ frame_name_to_base_query_map: PyLegendDict[str, QuerySpecification],
140
+ config: FrameToSqlConfig
141
+ ) -> Expression:
142
+ return FunctionCall(
143
+ name=QualifiedName(parts=["lead"]),
144
+ distinct=False,
145
+ arguments=[super().column_sql_expression(column, frame_name_to_base_query_map, config)],
146
+ filter_=None,
147
+ window=None
148
+ )
149
+
150
+
151
+ class LegendQLApiLagRow(LegendQLApiTdsRow):
152
+ __partial_frame: LegendQLApiPartialFrame
153
+ __row: "LegendQLApiTdsRow"
154
+
155
+ def __init__(
156
+ self,
157
+ partial_frame: LegendQLApiPartialFrame,
158
+ row: "LegendQLApiTdsRow"
159
+ ) -> None:
160
+ super().__init__(frame_name=row.get_frame_name(), frame=partial_frame.get_base_frame())
161
+ self.__partial_frame = partial_frame
162
+ self.__row = row
163
+
164
+ def to_pure_expression(self, config: FrameToPureConfig) -> str:
165
+ return f"{self.__partial_frame.to_pure_expression(config)}->lag({self.__row.to_pure_expression(config)})"
166
+
167
+ def column_sql_expression(
168
+ self,
169
+ column: str,
170
+ frame_name_to_base_query_map: PyLegendDict[str, QuerySpecification],
171
+ config: FrameToSqlConfig
172
+ ) -> Expression:
173
+ return FunctionCall(
174
+ name=QualifiedName(parts=["lag"]),
175
+ distinct=False,
176
+ arguments=[super().column_sql_expression(column, frame_name_to_base_query_map, config)],
177
+ filter_=None,
178
+ window=None
179
+ )
180
+
181
+
182
+ class LegendQLApiFirstRow(LegendQLApiTdsRow):
183
+ __partial_frame: LegendQLApiPartialFrame
184
+ __window_ref: LegendQLApiWindowReference
185
+ __row: "LegendQLApiTdsRow"
186
+
187
+ def __init__(
188
+ self,
189
+ partial_frame: LegendQLApiPartialFrame,
190
+ window_ref: LegendQLApiWindowReference,
191
+ row: "LegendQLApiTdsRow"
192
+ ) -> None:
193
+ super().__init__(frame_name=row.get_frame_name(), frame=partial_frame.get_base_frame())
194
+ self.__partial_frame = partial_frame
195
+ self.__window_ref = window_ref
196
+ self.__row = row
197
+
198
+ def to_pure_expression(self, config: FrameToPureConfig) -> str:
199
+ return (f"{self.__partial_frame.to_pure_expression(config)}->first("
200
+ f"{self.__window_ref.to_pure_expression(config)}, {self.__row.to_pure_expression(config)})")
201
+
202
+ def column_sql_expression(
203
+ self,
204
+ column: str,
205
+ frame_name_to_base_query_map: PyLegendDict[str, QuerySpecification],
206
+ config: FrameToSqlConfig
207
+ ) -> Expression:
208
+ return FunctionCall(
209
+ name=QualifiedName(parts=["first_value"]),
210
+ distinct=False,
211
+ arguments=[super().column_sql_expression(column, frame_name_to_base_query_map, config)],
212
+ filter_=None,
213
+ window=None
214
+ )
215
+
216
+
217
+ class LegendQLApiLastRow(LegendQLApiTdsRow):
218
+ __partial_frame: LegendQLApiPartialFrame
219
+ __window_ref: LegendQLApiWindowReference
220
+ __row: "LegendQLApiTdsRow"
221
+
222
+ def __init__(
223
+ self,
224
+ partial_frame: LegendQLApiPartialFrame,
225
+ window_ref: LegendQLApiWindowReference,
226
+ row: "LegendQLApiTdsRow"
227
+ ) -> None:
228
+ super().__init__(frame_name=row.get_frame_name(), frame=partial_frame.get_base_frame())
229
+ self.__partial_frame = partial_frame
230
+ self.__window_ref = window_ref
231
+ self.__row = row
232
+
233
+ def to_pure_expression(self, config: FrameToPureConfig) -> str:
234
+ return (f"{self.__partial_frame.to_pure_expression(config)}->last("
235
+ f"{self.__window_ref.to_pure_expression(config)}, {self.__row.to_pure_expression(config)})")
236
+
237
+ def column_sql_expression(
238
+ self,
239
+ column: str,
240
+ frame_name_to_base_query_map: PyLegendDict[str, QuerySpecification],
241
+ config: FrameToSqlConfig
242
+ ) -> Expression:
243
+ return FunctionCall(
244
+ name=QualifiedName(parts=["last_value"]),
245
+ distinct=False,
246
+ arguments=[super().column_sql_expression(column, frame_name_to_base_query_map, config)],
247
+ filter_=None,
248
+ window=None
249
+ )
250
+
251
+
252
+ class LegendQLApiNthRow(LegendQLApiTdsRow):
253
+ __partial_frame: LegendQLApiPartialFrame
254
+ __window_ref: LegendQLApiWindowReference
255
+ __row: "LegendQLApiTdsRow"
256
+ __offset: int
257
+
258
+ def __init__(
259
+ self,
260
+ partial_frame: LegendQLApiPartialFrame,
261
+ window_ref: LegendQLApiWindowReference,
262
+ row: "LegendQLApiTdsRow",
263
+ offset: int
264
+ ) -> None:
265
+ super().__init__(frame_name=row.get_frame_name(), frame=partial_frame.get_base_frame())
266
+ self.__partial_frame = partial_frame
267
+ self.__window_ref = window_ref
268
+ self.__row = row
269
+ self.__offset = offset
270
+
271
+ def to_pure_expression(self, config: FrameToPureConfig) -> str:
272
+ return (
273
+ f"{self.__partial_frame.to_pure_expression(config)}->nth("
274
+ f"{self.__window_ref.to_pure_expression(config)}, {self.__row.to_pure_expression(config)}, {self.__offset})"
275
+ )
276
+
277
+ def column_sql_expression(
278
+ self,
279
+ column: str,
280
+ frame_name_to_base_query_map: PyLegendDict[str, QuerySpecification],
281
+ config: FrameToSqlConfig
282
+ ) -> Expression:
283
+ return FunctionCall(
284
+ name=QualifiedName(parts=["nth_value"]),
285
+ distinct=False,
286
+ arguments=[
287
+ super().column_sql_expression(column, frame_name_to_base_query_map, config),
288
+ IntegerLiteral(self.__offset)
289
+ ],
290
+ filter_=None,
291
+ window=None
292
+ )
@@ -0,0 +1,13 @@
1
+ # Copyright 2025 Goldman Sachs
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
@@ -18,7 +18,7 @@ from pylegend._typing import (
18
18
  PyLegendSequence,
19
19
  PyLegendDict,
20
20
  )
21
- from pylegend.core.language.expression import (
21
+ from pylegend.core.language.shared.expression import (
22
22
  PyLegendExpression,
23
23
  PyLegendExpressionBooleanReturn,
24
24
  PyLegendExpressionStringReturn,
@@ -32,9 +32,13 @@ from pylegend.core.language.expression import (
32
32
  from pylegend.core.sql.metamodel import (
33
33
  Expression,
34
34
  QuerySpecification,
35
- SingleColumn,
36
35
  )
37
36
  from pylegend.core.tds.tds_frame import FrameToSqlConfig
37
+ from pylegend.core.tds.tds_frame import FrameToPureConfig
38
+ from pylegend.core.language.shared.helpers import escape_column_name
39
+ from typing import TYPE_CHECKING
40
+ if TYPE_CHECKING:
41
+ from pylegend.core.language.shared.tds_row import AbstractTdsRow
38
42
 
39
43
 
40
44
  __all__: PyLegendSequence[str] = [
@@ -51,11 +55,11 @@ __all__: PyLegendSequence[str] = [
51
55
 
52
56
 
53
57
  class PyLegendColumnExpression(PyLegendExpression, metaclass=ABCMeta):
54
- __frame_name: str
58
+ __row: "AbstractTdsRow"
55
59
  __column: str
56
60
 
57
- def __init__(self, frame_name: str, column: str) -> None:
58
- self.__frame_name = frame_name
61
+ def __init__(self, row: "AbstractTdsRow", column: str) -> None:
62
+ self.__row = row
59
63
  self.__column = column
60
64
 
61
65
  def to_sql_expression(
@@ -63,61 +67,58 @@ class PyLegendColumnExpression(PyLegendExpression, metaclass=ABCMeta):
63
67
  frame_name_to_base_query_map: PyLegendDict[str, QuerySpecification],
64
68
  config: FrameToSqlConfig
65
69
  ) -> Expression:
66
- query = frame_name_to_base_query_map[self.__frame_name]
67
- db_extension = config.sql_to_string_generator().get_db_extension()
68
- filtered = [
69
- s for s in query.select.selectItems
70
- if (isinstance(s, SingleColumn) and
71
- s.alias == db_extension.quote_identifier(self.__column))
72
- ]
73
- if len(filtered) == 0:
74
- raise RuntimeError("Cannot find column: " + self.__column) # pragma: no cover
75
- return filtered[0].expression
70
+ return self.__row.column_sql_expression(self.__column, frame_name_to_base_query_map, config)
71
+
72
+ def to_pure_expression(self, config: FrameToPureConfig) -> str:
73
+ return f"{self.__row.to_pure_expression(config)}.{escape_column_name(self.__column)}"
74
+
75
+ def get_column(self) -> str:
76
+ return self.__column
76
77
 
77
78
 
78
79
  class PyLegendBooleanColumnExpression(PyLegendColumnExpression, PyLegendExpressionBooleanReturn):
79
80
 
80
- def __init__(self, frame_name: str, column: str) -> None:
81
- super().__init__(frame_name=frame_name, column=column)
81
+ def __init__(self, row: "AbstractTdsRow", column: str) -> None:
82
+ super().__init__(row=row, column=column)
82
83
 
83
84
 
84
85
  class PyLegendStringColumnExpression(PyLegendColumnExpression, PyLegendExpressionStringReturn):
85
86
 
86
- def __init__(self, frame_name: str, column: str) -> None:
87
- super().__init__(frame_name=frame_name, column=column)
87
+ def __init__(self, row: "AbstractTdsRow", column: str) -> None:
88
+ super().__init__(row=row, column=column)
88
89
 
89
90
 
90
91
  class PyLegendNumberColumnExpression(PyLegendColumnExpression, PyLegendExpressionNumberReturn):
91
92
 
92
- def __init__(self, frame_name: str, column: str) -> None:
93
- super().__init__(frame_name=frame_name, column=column)
93
+ def __init__(self, row: "AbstractTdsRow", column: str) -> None:
94
+ super().__init__(row=row, column=column)
94
95
 
95
96
 
96
97
  class PyLegendIntegerColumnExpression(PyLegendNumberColumnExpression, PyLegendExpressionIntegerReturn):
97
98
 
98
- def __init__(self, frame_name: str, column: str) -> None:
99
- super().__init__(frame_name=frame_name, column=column)
99
+ def __init__(self, row: "AbstractTdsRow", column: str) -> None:
100
+ super().__init__(row=row, column=column)
100
101
 
101
102
 
102
103
  class PyLegendFloatColumnExpression(PyLegendNumberColumnExpression, PyLegendExpressionFloatReturn):
103
104
 
104
- def __init__(self, frame_name: str, column: str) -> None:
105
- super().__init__(frame_name=frame_name, column=column)
105
+ def __init__(self, row: "AbstractTdsRow", column: str) -> None:
106
+ super().__init__(row=row, column=column)
106
107
 
107
108
 
108
109
  class PyLegendDateColumnExpression(PyLegendColumnExpression, PyLegendExpressionDateReturn):
109
110
 
110
- def __init__(self, frame_name: str, column: str) -> None:
111
- super().__init__(frame_name=frame_name, column=column)
111
+ def __init__(self, row: "AbstractTdsRow", column: str) -> None:
112
+ super().__init__(row=row, column=column)
112
113
 
113
114
 
114
115
  class PyLegendDateTimeColumnExpression(PyLegendDateColumnExpression, PyLegendExpressionDateTimeReturn):
115
116
 
116
- def __init__(self, frame_name: str, column: str) -> None:
117
- super().__init__(frame_name=frame_name, column=column)
117
+ def __init__(self, row: "AbstractTdsRow", column: str) -> None:
118
+ super().__init__(row=row, column=column)
118
119
 
119
120
 
120
121
  class PyLegendStrictDateColumnExpression(PyLegendDateColumnExpression, PyLegendExpressionStrictDateReturn):
121
122
 
122
- def __init__(self, frame_name: str, column: str) -> None:
123
- super().__init__(frame_name=frame_name, column=column)
123
+ def __init__(self, row: "AbstractTdsRow", column: str) -> None:
124
+ super().__init__(row=row, column=column)
@@ -23,6 +23,7 @@ from pylegend.core.sql.metamodel import (
23
23
  QuerySpecification,
24
24
  )
25
25
  from pylegend.core.tds.tds_frame import FrameToSqlConfig
26
+ from pylegend.core.tds.tds_frame import FrameToPureConfig
26
27
 
27
28
 
28
29
  __all__: PyLegendSequence[str] = [
@@ -47,6 +48,13 @@ class PyLegendExpression(metaclass=ABCMeta):
47
48
  ) -> Expression:
48
49
  pass
49
50
 
51
+ @abstractmethod
52
+ def to_pure_expression(self, config: FrameToPureConfig) -> str:
53
+ pass
54
+
55
+ def is_non_nullable(self) -> bool:
56
+ return False
57
+
50
58
 
51
59
  class PyLegendExpressionBooleanReturn(PyLegendExpression, metaclass=ABCMeta):
52
60
  pass
@@ -16,17 +16,22 @@
16
16
  from pylegend._typing import (
17
17
  PyLegendSequence,
18
18
  )
19
- from pylegend.core.language.primitives.strictdate import PyLegendStrictDate
20
- from pylegend.core.language.primitives.datetime import PyLegendDateTime
21
- from pylegend.core.language.operations.date_operation_expressions import (
19
+ from pylegend.core.language.shared.primitives.strictdate import PyLegendStrictDate
20
+ from pylegend.core.language.shared.primitives.datetime import PyLegendDateTime
21
+ from pylegend.core.language.shared.primitives.string import PyLegendString
22
+ from pylegend.core.language.shared.operations.date_operation_expressions import (
22
23
  PyLegendTodayExpression,
23
24
  PyLegendNowExpression,
24
25
  )
26
+ from pylegend.core.language.shared.operations.string_operation_expressions import (
27
+ PyLegendCurrentUserExpression,
28
+ )
25
29
 
26
30
 
27
31
  __all__: PyLegendSequence[str] = [
28
32
  "today",
29
33
  "now",
34
+ "current_user",
30
35
  ]
31
36
 
32
37
 
@@ -36,3 +41,7 @@ def today() -> PyLegendStrictDate:
36
41
 
37
42
  def now() -> PyLegendDateTime:
38
43
  return PyLegendDateTime(PyLegendNowExpression())
44
+
45
+
46
+ def current_user() -> PyLegendString:
47
+ return PyLegendString(PyLegendCurrentUserExpression())
@@ -0,0 +1,75 @@
1
+ # Copyright 2025 Goldman Sachs
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from pylegend._typing import PyLegendList
16
+ __all__ = [
17
+ "generate_pure_functional_call",
18
+ "generate_pure_lambda",
19
+ "escape_column_name",
20
+ "expr_has_matching_start_and_end_parentheses",
21
+ ]
22
+
23
+
24
+ def generate_pure_functional_call(
25
+ func: str,
26
+ params: PyLegendList[str],
27
+ force_prefix: bool = False,
28
+ auto_map: bool = False
29
+ ) -> str:
30
+ should_prefix = force_prefix or (len(params) == 0)
31
+
32
+ updated_params: PyLegendList[str] = []
33
+ for param in params:
34
+ if param.startswith("(") and param.endswith(")"):
35
+ if expr_has_matching_start_and_end_parentheses(param):
36
+ updated_params.append(param[1:-1])
37
+ else:
38
+ updated_params.append(param)
39
+ else:
40
+ updated_params.append(param)
41
+
42
+ if auto_map and len(updated_params) == 1:
43
+ return f"{params[0]}->map(op | {generate_pure_functional_call(func, ['$op'], force_prefix, False)})"
44
+
45
+ if auto_map and len(updated_params) == 2:
46
+ new_params = ['$op'] + [updated_params[1]]
47
+ return f"{params[0]}->map(op | {generate_pure_functional_call(func, new_params, force_prefix, False)})"
48
+
49
+ if should_prefix:
50
+ return f"{func}({', '.join(updated_params)})"
51
+ else:
52
+ return f"{params[0]}->{func}({', '.join(updated_params[1:])})"
53
+
54
+
55
+ def generate_pure_lambda(param_name: str, expr: str, wrap_in_braces: bool = True) -> str:
56
+ lambda_code = param_name + " | " + (expr[1:-1] if expr_has_matching_start_and_end_parentheses(expr) else expr)
57
+ return "{" + lambda_code + "}" if wrap_in_braces else lambda_code
58
+
59
+
60
+ def escape_column_name(name: str) -> str:
61
+ return (name if name.isidentifier() else "'" + name.replace("'", "\\'") + "'")
62
+
63
+
64
+ def expr_has_matching_start_and_end_parentheses(expr: str) -> bool:
65
+ if expr.startswith("(") and expr.endswith(")"):
66
+ bracket_indices = [0]
67
+ for (i, char) in enumerate(expr[1:-1]):
68
+ if char == "(":
69
+ bracket_indices.append(i + 1)
70
+ elif char == ")":
71
+ if bracket_indices:
72
+ bracket_indices.pop()
73
+ return (len(bracket_indices) == 1) and (bracket_indices[0] == 0)
74
+ else:
75
+ return False
@@ -19,7 +19,7 @@ from pylegend._typing import (
19
19
  PyLegendDict,
20
20
  PyLegendUnion,
21
21
  )
22
- from pylegend.core.language.expression import (
22
+ from pylegend.core.language.shared.expression import (
23
23
  PyLegendExpression,
24
24
  PyLegendExpressionBooleanReturn,
25
25
  PyLegendExpressionStringReturn,
@@ -39,6 +39,7 @@ from pylegend.core.sql.metamodel import (
39
39
  ColumnType,
40
40
  )
41
41
  from pylegend.core.tds.tds_frame import FrameToSqlConfig
42
+ from pylegend.core.tds.tds_frame import FrameToPureConfig
42
43
 
43
44
 
44
45
  __all__: PyLegendSequence[str] = [
@@ -65,6 +66,12 @@ class PyLegendBooleanLiteralExpression(PyLegendExpressionBooleanReturn):
65
66
  ) -> Expression:
66
67
  return BooleanLiteral(value=self.__value)
67
68
 
69
+ def to_pure_expression(self, config: FrameToPureConfig) -> str:
70
+ return "true" if self.__value else "false"
71
+
72
+ def is_non_nullable(self) -> bool:
73
+ return True
74
+
68
75
 
69
76
  class PyLegendStringLiteralExpression(PyLegendExpressionStringReturn):
70
77
  __value: str
@@ -79,6 +86,13 @@ class PyLegendStringLiteralExpression(PyLegendExpressionStringReturn):
79
86
  ) -> Expression:
80
87
  return StringLiteral(value=self.__value, quoted=False)
81
88
 
89
+ def to_pure_expression(self, config: FrameToPureConfig) -> str:
90
+ escaped = self.__value.replace("'", "\\'")
91
+ return f"'{escaped}'"
92
+
93
+ def is_non_nullable(self) -> bool:
94
+ return True
95
+
82
96
 
83
97
  class PyLegendIntegerLiteralExpression(PyLegendExpressionIntegerReturn):
84
98
  __value: int
@@ -93,6 +107,12 @@ class PyLegendIntegerLiteralExpression(PyLegendExpressionIntegerReturn):
93
107
  ) -> Expression:
94
108
  return IntegerLiteral(value=self.__value)
95
109
 
110
+ def to_pure_expression(self, config: FrameToPureConfig) -> str:
111
+ return str(self.__value)
112
+
113
+ def is_non_nullable(self) -> bool:
114
+ return True
115
+
96
116
 
97
117
  class PyLegendFloatLiteralExpression(PyLegendExpressionFloatReturn):
98
118
  __value: float
@@ -107,6 +127,12 @@ class PyLegendFloatLiteralExpression(PyLegendExpressionFloatReturn):
107
127
  ) -> Expression:
108
128
  return DoubleLiteral(value=self.__value)
109
129
 
130
+ def to_pure_expression(self, config: FrameToPureConfig) -> str:
131
+ return str(self.__value)
132
+
133
+ def is_non_nullable(self) -> bool:
134
+ return True
135
+
110
136
 
111
137
  class PyLegendDateTimeLiteralExpression(PyLegendExpressionDateTimeReturn):
112
138
  __value: datetime
@@ -124,6 +150,12 @@ class PyLegendDateTimeLiteralExpression(PyLegendExpressionDateTimeReturn):
124
150
  type_=ColumnType(name="TIMESTAMP", parameters=[])
125
151
  )
126
152
 
153
+ def to_pure_expression(self, config: FrameToPureConfig) -> str:
154
+ return f"%{self.__value.isoformat()}"
155
+
156
+ def is_non_nullable(self) -> bool:
157
+ return True
158
+
127
159
 
128
160
  class PyLegendStrictDateLiteralExpression(PyLegendExpressionStrictDateReturn):
129
161
  __value: date
@@ -141,6 +173,12 @@ class PyLegendStrictDateLiteralExpression(PyLegendExpressionStrictDateReturn):
141
173
  type_=ColumnType(name="DATE", parameters=[])
142
174
  )
143
175
 
176
+ def to_pure_expression(self, config: FrameToPureConfig) -> str:
177
+ return f"%{self.__value.isoformat()}"
178
+
179
+ def is_non_nullable(self) -> bool:
180
+ return True
181
+
144
182
 
145
183
  def convert_literal_to_literal_expression(
146
184
  literal: PyLegendUnion[int, float, bool, str, datetime, date]