pylegend 0.3.0__py3-none-any.whl → 0.4.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 (123) hide show
  1. pylegend/__init__.py +7 -5
  2. pylegend/core/{databse → database}/sql_to_string/__init__.py +3 -3
  3. pylegend/core/{databse → database}/sql_to_string/db_extension.py +11 -5
  4. pylegend/core/{databse → database}/sql_to_string/generator.py +2 -2
  5. pylegend/core/language/__init__.py +10 -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} +3 -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 +164 -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/{operations → shared/operations}/primitive_operation_expressions.py +42 -3
  27. pylegend/core/language/{operations → shared/operations}/string_operation_expressions.py +169 -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 +23 -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 +25 -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 +16 -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.4.0.dist-info}/METADATA +7 -8
  109. pylegend-0.4.0.dist-info/NOTICE +5 -0
  110. pylegend-0.4.0.dist-info/RECORD +155 -0
  111. {pylegend-0.3.0.dist-info → pylegend-0.4.0.dist-info}/WHEEL +1 -1
  112. pylegend/core/tds/legend_api/frames/legend_api_base_tds_frame.py +0 -294
  113. pylegend-0.3.0.dist-info/RECORD +0 -115
  114. /pylegend/core/{databse → database}/__init__.py +0 -0
  115. /pylegend/core/{databse → database}/sql_to_string/config.py +0 -0
  116. /pylegend/core/language/{operations → shared/operations}/__init__.py +0 -0
  117. /pylegend/core/tds/{legend_api → legacy_api}/__init__.py +0 -0
  118. /pylegend/core/tds/{legend_api → legacy_api}/frames/__init__.py +0 -0
  119. /pylegend/core/tds/{legend_api → legacy_api}/frames/functions/__init__.py +0 -0
  120. /pylegend/extensions/tds/{legend_api → legacy_api}/__init__.py +0 -0
  121. /pylegend/extensions/tds/{legend_api → legacy_api}/frames/__init__.py +0 -0
  122. {pylegend-0.3.0.dist-info → pylegend-0.4.0.dist-info}/LICENSE +0 -0
  123. {pylegend-0.3.0.dist-info → pylegend-0.4.0.dist-info}/LICENSE.spdx +0 -0
@@ -0,0 +1,169 @@
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 datetime import date, datetime
16
+ from pylegend._typing import (
17
+ PyLegendList,
18
+ PyLegendSequence,
19
+ PyLegendCallable,
20
+ PyLegendUnion,
21
+ PyLegendTuple,
22
+ )
23
+ from pylegend.core.language.legendql_api.legendql_api_tds_row import LegendQLApiTdsRow
24
+ from pylegend.core.tds.legendql_api.frames.legendql_api_applied_function_tds_frame import LegendQLApiAppliedFunction
25
+ from pylegend.core.tds.sql_query_helpers import create_sub_query
26
+ from pylegend.core.sql.metamodel import (
27
+ QuerySpecification,
28
+ SingleColumn,
29
+ SelectItem,
30
+ )
31
+ from pylegend.core.tds.tds_column import TdsColumn
32
+ from pylegend.core.tds.tds_frame import FrameToSqlConfig
33
+ from pylegend.core.tds.tds_frame import FrameToPureConfig
34
+ from pylegend.core.tds.legendql_api.frames.legendql_api_base_tds_frame import LegendQLApiBaseTdsFrame
35
+ from pylegend.core.language import (
36
+ PyLegendPrimitive,
37
+ PyLegendPrimitiveOrPythonPrimitive,
38
+ convert_literal_to_literal_expression,
39
+ )
40
+ from pylegend.core.language.shared.helpers import generate_pure_lambda, escape_column_name
41
+ from pylegend.core.tds.abstract.function_helpers import tds_column_for_primitive
42
+
43
+ __all__: PyLegendSequence[str] = [
44
+ "LegendQLApiProjectFunction"
45
+ ]
46
+
47
+
48
+ class LegendQLApiProjectFunction(LegendQLApiAppliedFunction):
49
+ __base_frame: LegendQLApiBaseTdsFrame
50
+ __new_column_expressions: PyLegendList[PyLegendTuple[str, PyLegendPrimitiveOrPythonPrimitive]]
51
+
52
+ @classmethod
53
+ def name(cls) -> str:
54
+ return "project"
55
+
56
+ def __init__(
57
+ self,
58
+ base_frame: LegendQLApiBaseTdsFrame,
59
+ project_columns: PyLegendUnion[
60
+ PyLegendTuple[
61
+ str,
62
+ PyLegendCallable[[LegendQLApiTdsRow], PyLegendPrimitiveOrPythonPrimitive]
63
+ ],
64
+ PyLegendList[
65
+ PyLegendTuple[
66
+ str,
67
+ PyLegendCallable[[LegendQLApiTdsRow], PyLegendPrimitiveOrPythonPrimitive]
68
+ ]
69
+ ]
70
+ ]
71
+ ) -> None:
72
+ self.__base_frame = base_frame
73
+ col_expressions: PyLegendList[PyLegendTuple[str, PyLegendPrimitiveOrPythonPrimitive]] = []
74
+ tds_row = LegendQLApiTdsRow.from_tds_frame("r", self.__base_frame)
75
+ for (i, project_col) in enumerate(project_columns if isinstance(project_columns, list) else [project_columns]):
76
+ if isinstance(project_col, tuple) and (len(project_col) == 2):
77
+ if not isinstance(project_col[0], str):
78
+ raise TypeError(
79
+ "'project' function project_columns argument incompatible. "
80
+ "First element in an project tuple should be a string (new column name). "
81
+ "E.g - ('new col', lambda r: r.c1 + 1). "
82
+ f"Element at index {i} (0-indexed) is incompatible"
83
+ )
84
+ if not isinstance(project_col[1], type(lambda x: 0)) or (project_col[1].__code__.co_argcount != 1):
85
+ raise TypeError(
86
+ "'project' function project_columns argument incompatible. "
87
+ "Second element in an project tuple should be a lambda function which takes one argument "
88
+ "(LegendQLApiTdsRow) E.g - ('new col', lambda r: r.c1 + 1)."
89
+ f"Element at index {i} (0-indexed) is incompatible"
90
+ )
91
+ try:
92
+ result = project_col[1](tds_row)
93
+ except Exception as e:
94
+ raise RuntimeError(
95
+ "'project' function project_columns argument incompatible. "
96
+ f"Error occurred while evaluating project lambda at index {i} (0-indexed). Message: " + str(e)
97
+ ) from e
98
+
99
+ if not isinstance(result, (int, float, bool, str, date, datetime, PyLegendPrimitive)):
100
+ raise TypeError(
101
+ "'project' function project_columns argument incompatible. "
102
+ f"Project lambda at index {i} (0-indexed) returns non-primitive - {str(type(result))}"
103
+ )
104
+ col_expressions.append((project_col[0], result))
105
+ else:
106
+ raise TypeError(
107
+ "'project' function project_columns argument should be a list of tuples with two elements - "
108
+ "first element being a string (new column name), second element being a lambda "
109
+ "function which takes one argument (LegendQLApiTdsRow) "
110
+ "E.g - [('new col1', lambda r: r.c1 + 1), ('new col2', lambda r: r.c2)]. "
111
+ f"Element at index {i} (0-indexed) is incompatible"
112
+ )
113
+ self.__new_column_expressions = col_expressions
114
+
115
+ def to_sql(self, config: FrameToSqlConfig) -> QuerySpecification:
116
+ base_query = self.__base_frame.to_sql_query_object(config)
117
+ db_extension = config.sql_to_string_generator().get_db_extension()
118
+ new_query = create_sub_query(base_query, config, "root")
119
+ new_select_items: PyLegendList[SelectItem] = []
120
+ for c in self.__new_column_expressions:
121
+ if isinstance(c[1], (bool, int, float, str, date, datetime)):
122
+ col_sql_expr = convert_literal_to_literal_expression(c[1]).to_sql_expression(
123
+ {"r": new_query},
124
+ config
125
+ )
126
+ else:
127
+ col_sql_expr = c[1].to_sql_expression({"r": new_query}, config)
128
+ new_select_items.append(
129
+ SingleColumn(alias=db_extension.quote_identifier(c[0]), expression=col_sql_expr)
130
+ )
131
+ new_query.select.selectItems = new_select_items
132
+ return new_query
133
+
134
+ def to_pure(self, config: FrameToPureConfig) -> str:
135
+ def render_single_column_expression(
136
+ c: PyLegendUnion[
137
+ PyLegendTuple[str, PyLegendPrimitiveOrPythonPrimitive],
138
+ PyLegendTuple[str, PyLegendPrimitiveOrPythonPrimitive, PyLegendPrimitive]
139
+ ]
140
+ ) -> str:
141
+ escaped_col_name = escape_column_name(c[0])
142
+ expr_str = (c[1].to_pure_expression(config) if isinstance(c[1], PyLegendPrimitive) else
143
+ convert_literal_to_literal_expression(c[1]).to_pure_expression(config))
144
+ return f"{escaped_col_name}:{generate_pure_lambda('r', expr_str)}"
145
+
146
+ project_str = (f"->project(~[{config.separator(2)}" +
147
+ ("," + config.separator(2, True)).join(
148
+ [render_single_column_expression(x) for x in self.__new_column_expressions]
149
+ ) +
150
+ f"{config.separator(1)}])")
151
+ return f"{self.__base_frame.to_pure(config)}{config.separator(1)}" + project_str
152
+
153
+ def base_frame(self) -> LegendQLApiBaseTdsFrame:
154
+ return self.__base_frame
155
+
156
+ def tds_frame_parameters(self) -> PyLegendList["LegendQLApiBaseTdsFrame"]:
157
+ return []
158
+
159
+ def calculate_columns(self) -> PyLegendSequence["TdsColumn"]:
160
+ new_columns = []
161
+ for c in self.__new_column_expressions:
162
+ new_columns.append(tds_column_for_primitive(c[0], c[1] if len(c) == 2 else c[2]))
163
+ return new_columns
164
+
165
+ def validate(self) -> bool:
166
+ col_names = [x[0] for x in self.__new_column_expressions]
167
+ if len(col_names) != len(set(col_names)):
168
+ raise ValueError(f"Project column names list has duplicates: {col_names}")
169
+ return True
@@ -0,0 +1,189 @@
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
+ PyLegendList,
17
+ PyLegendSequence,
18
+ PyLegendCallable,
19
+ PyLegendUnion,
20
+ PyLegendTuple,
21
+ )
22
+ from pylegend.core.language import PyLegendColumnExpression
23
+ from pylegend.core.language.legendql_api.legendql_api_custom_expressions import LegendQLApiPrimitive
24
+ from pylegend.core.language.legendql_api.legendql_api_tds_row import LegendQLApiTdsRow
25
+ from pylegend.core.tds.legendql_api.frames.legendql_api_applied_function_tds_frame import LegendQLApiAppliedFunction
26
+ from pylegend.core.tds.sql_query_helpers import copy_query
27
+ from pylegend.core.sql.metamodel import (
28
+ QuerySpecification,
29
+ SingleColumn,
30
+ SelectItem,
31
+ )
32
+ from pylegend.core.tds.tds_column import TdsColumn
33
+ from pylegend.core.tds.tds_frame import FrameToSqlConfig
34
+ from pylegend.core.tds.tds_frame import FrameToPureConfig
35
+ from pylegend.core.tds.legendql_api.frames.legendql_api_base_tds_frame import LegendQLApiBaseTdsFrame
36
+ from pylegend.core.language.shared.helpers import escape_column_name
37
+
38
+
39
+ __all__: PyLegendSequence[str] = [
40
+ "LegendQLApiRenameFunction"
41
+ ]
42
+
43
+
44
+ class LegendQLApiRenameFunction(LegendQLApiAppliedFunction):
45
+ __base_frame: LegendQLApiBaseTdsFrame
46
+ __column_names: PyLegendList[str]
47
+ __renamed_column_names: PyLegendList[str]
48
+
49
+ @classmethod
50
+ def name(cls) -> str:
51
+ return "renameColumns"
52
+
53
+ def __init__(
54
+ self,
55
+ base_frame: LegendQLApiBaseTdsFrame,
56
+ column_renames: PyLegendUnion[
57
+ PyLegendTuple[str, str],
58
+ PyLegendList[PyLegendTuple[str, str]],
59
+ PyLegendCallable[
60
+ [LegendQLApiTdsRow],
61
+ PyLegendUnion[
62
+ PyLegendTuple[LegendQLApiPrimitive, str],
63
+ PyLegendList[PyLegendTuple[LegendQLApiPrimitive, str]]
64
+ ]
65
+ ]
66
+ ]
67
+ ) -> None:
68
+ self.__base_frame = base_frame
69
+
70
+ col_names: PyLegendList[str] = []
71
+ renamed_col_names: PyLegendList[str] = []
72
+
73
+ def rename_tuple_check(t): # type: ignore
74
+ return isinstance(t, tuple) and (len(t) == 2) and isinstance(t[0], str) and isinstance(t[1], str)
75
+
76
+ if rename_tuple_check(column_renames): # type: ignore
77
+ col_names.append(column_renames[0]) # type: ignore
78
+ renamed_col_names.append(column_renames[1]) # type: ignore
79
+
80
+ elif isinstance(column_renames, list) and all([rename_tuple_check(r) for r in column_renames]): # type: ignore
81
+ for t in column_renames:
82
+ col_names.append(t[0])
83
+ renamed_col_names.append(t[1])
84
+
85
+ elif isinstance(column_renames, type(lambda x: 0)) and (column_renames.__code__.co_argcount == 1):
86
+ tds_row = LegendQLApiTdsRow.from_tds_frame("frame", self.__base_frame)
87
+ try:
88
+ result = column_renames(tds_row)
89
+ except Exception as e:
90
+ raise RuntimeError(
91
+ "rename' function column_renames argument lambda incompatible. "
92
+ "Error occurred while evaluating. Message: " + str(e)
93
+ ) from e
94
+
95
+ list_result = result if isinstance(result, list) else [result]
96
+ for (i, r) in enumerate(list_result):
97
+ if (isinstance(r, tuple) and (len(r) == 2) and
98
+ isinstance(r[0], LegendQLApiPrimitive) and isinstance(r[0].value(), PyLegendColumnExpression)
99
+ and isinstance(r[1], str)):
100
+ col_expr: PyLegendColumnExpression = r[0].value()
101
+ col_names.append(col_expr.get_column())
102
+ renamed_col_names.append(r[1])
103
+ else:
104
+ raise TypeError(
105
+ "'rename' function column_renames argument lambda incompatible. Each element in rename list "
106
+ "should be a tuple with first element being a simple column expression and "
107
+ "second element being a string (renamed column name) "
108
+ f"(E.g - lambda r: [(r.c1, 'c2')]). Element at index {i} (0-indexed) is incompatible"
109
+ )
110
+
111
+ else:
112
+ raise TypeError("'rename' function column_renames argument can either be a list of renaming tuples or "
113
+ "a lambda function which takes one argument (LegendQLApiTdsRow)")
114
+
115
+ self.__column_names = col_names
116
+ self.__renamed_column_names = renamed_col_names
117
+
118
+ def to_sql(self, config: FrameToSqlConfig) -> QuerySpecification:
119
+ base_query = self.__base_frame.to_sql_query_object(config)
120
+
121
+ db_extension = config.sql_to_string_generator().get_db_extension()
122
+ quoted_column_names_to_change = [db_extension.quote_identifier(s) for s in self.__column_names]
123
+ quoted_renamed_column_names = [db_extension.quote_identifier(s) for s in self.__renamed_column_names]
124
+
125
+ new_select_items: PyLegendList[SelectItem] = []
126
+ for col in base_query.select.selectItems:
127
+ if not isinstance(col, SingleColumn):
128
+ raise ValueError("Rename columns operation not supported for queries "
129
+ "with columns other than SingleColumn") # pragma: no cover
130
+ if col.alias is None:
131
+ raise ValueError("Rename columns operation not supported for queries "
132
+ "with SingleColumns with missing alias") # pragma: no cover
133
+ if col.alias in quoted_column_names_to_change:
134
+ new_alias = quoted_renamed_column_names[quoted_column_names_to_change.index(col.alias)]
135
+ new_select_items.append(SingleColumn(alias=new_alias, expression=col.expression))
136
+ else:
137
+ new_select_items.append(col)
138
+
139
+ new_query = copy_query(base_query)
140
+ new_query.select.selectItems = new_select_items
141
+ return new_query
142
+
143
+ def to_pure(self, config: FrameToPureConfig) -> str:
144
+ return (f"{self.__base_frame.to_pure(config)}{config.separator(1)}" +
145
+ f"{config.separator(1)}".join([
146
+ f"->rename(~{x}, ~{y})"
147
+ for x, y in zip(
148
+ map(escape_column_name, self.__column_names),
149
+ map(escape_column_name, self.__renamed_column_names)
150
+ )
151
+ ]))
152
+
153
+ def base_frame(self) -> LegendQLApiBaseTdsFrame:
154
+ return self.__base_frame
155
+
156
+ def tds_frame_parameters(self) -> PyLegendList["LegendQLApiBaseTdsFrame"]:
157
+ return []
158
+
159
+ def calculate_columns(self) -> PyLegendSequence["TdsColumn"]:
160
+ new_columns = []
161
+ for base_col in self.__base_frame.columns():
162
+ if base_col.get_name() in self.__column_names:
163
+ renamed_column_name = self.__renamed_column_names[self.__column_names.index(base_col.get_name())]
164
+ new_columns.append(base_col.copy_with_changed_name(renamed_column_name))
165
+ else:
166
+ new_columns.append(base_col.copy())
167
+ return new_columns
168
+
169
+ def validate(self) -> bool:
170
+ if len(self.__column_names) != len(self.__renamed_column_names):
171
+ raise ValueError(
172
+ "column_names list and renamed_column_names list should have same size when renaming columns.\n"
173
+ f"column_names list - (Count: {len(self.__column_names)}) - {self.__column_names}\n"
174
+ f"renamed_column_names_list - (Count: {len(self.__renamed_column_names)}) - {self.__renamed_column_names}\n"
175
+ )
176
+
177
+ if len(self.__column_names) != len(set(self.__column_names)):
178
+ raise ValueError(
179
+ "column_names list shouldn't have duplicates when renaming columns.\n"
180
+ f"column_names list - (Count: {len(self.__column_names)}) - {self.__column_names}\n"
181
+ )
182
+
183
+ if len(self.__renamed_column_names) != len(set(self.__renamed_column_names)):
184
+ raise ValueError(
185
+ "renamed_column_names_list list shouldn't have duplicates when renaming columns.\n"
186
+ f"renamed_column_names_list - (Count: {len(self.__renamed_column_names)}) - {self.__renamed_column_names}\n"
187
+ )
188
+
189
+ return True
@@ -0,0 +1,131 @@
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
+ PyLegendList,
17
+ PyLegendSequence,
18
+ PyLegendTuple,
19
+ PyLegendCallable,
20
+ PyLegendUnion,
21
+ )
22
+ from pylegend.core.language.legendql_api.legendql_api_custom_expressions import LegendQLApiPrimitive
23
+ from pylegend.core.language.legendql_api.legendql_api_tds_row import LegendQLApiTdsRow
24
+ from pylegend.core.tds.legendql_api.frames.functions.legendql_api_function_helpers import infer_columns_from_frame
25
+ from pylegend.core.tds.legendql_api.frames.legendql_api_applied_function_tds_frame import LegendQLApiAppliedFunction
26
+ from pylegend.core.tds.sql_query_helpers import copy_query, create_sub_query
27
+ from pylegend.core.sql.metamodel import (
28
+ QuerySpecification,
29
+ SingleColumn,
30
+ SelectItem
31
+ )
32
+ from pylegend.core.tds.tds_column import TdsColumn
33
+ from pylegend.core.tds.tds_frame import FrameToSqlConfig
34
+ from pylegend.core.tds.tds_frame import FrameToPureConfig
35
+ from pylegend.core.tds.legendql_api.frames.legendql_api_base_tds_frame import LegendQLApiBaseTdsFrame
36
+ from pylegend.core.language.shared.helpers import escape_column_name
37
+
38
+
39
+ __all__: PyLegendSequence[str] = [
40
+ "LegendQLApiSelectFunction"
41
+ ]
42
+
43
+
44
+ class LegendQLApiSelectFunction(LegendQLApiAppliedFunction):
45
+ __base_frame: LegendQLApiBaseTdsFrame
46
+ __column_name_list: PyLegendList[str]
47
+
48
+ @classmethod
49
+ def name(cls) -> str:
50
+ return "select"
51
+
52
+ def __init__(
53
+ self,
54
+ base_frame: LegendQLApiBaseTdsFrame,
55
+ columns: PyLegendUnion[
56
+ str,
57
+ PyLegendList[str],
58
+ PyLegendCallable[
59
+ [LegendQLApiTdsRow],
60
+ PyLegendUnion[LegendQLApiPrimitive, PyLegendList[LegendQLApiPrimitive]]
61
+ ]
62
+ ]
63
+ ) -> None:
64
+ self.__base_frame = base_frame
65
+ self.__column_name_list = infer_columns_from_frame(base_frame, columns, "'select' function 'columns'")
66
+
67
+ def to_sql(self, config: FrameToSqlConfig) -> QuerySpecification:
68
+ base_query = self.__base_frame.to_sql_query_object(config)
69
+ db_extension = config.sql_to_string_generator().get_db_extension()
70
+ columns_to_retain = [db_extension.quote_identifier(x) for x in self.__column_name_list]
71
+
72
+ sub_query_required = (len(base_query.groupBy) > 0) or (len(base_query.orderBy) > 0) or \
73
+ (base_query.having is not None) or base_query.select.distinct
74
+
75
+ if sub_query_required:
76
+ new_query = create_sub_query(base_query, config, "root", columns_to_retain=columns_to_retain)
77
+ return new_query
78
+ else:
79
+ new_cols_with_index: PyLegendList[PyLegendTuple[int, 'SelectItem']] = []
80
+ for col in base_query.select.selectItems:
81
+ if not isinstance(col, SingleColumn):
82
+ raise ValueError("Select operation not supported for queries "
83
+ "with columns other than SingleColumn") # pragma: no cover
84
+ if col.alias is None:
85
+ raise ValueError("Select operation not supported for queries "
86
+ "with SingleColumns with missing alias") # pragma: no cover
87
+ if col.alias in columns_to_retain:
88
+ new_cols_with_index.append((columns_to_retain.index(col.alias), col))
89
+
90
+ new_select_items = [y[1] for y in sorted(new_cols_with_index, key=lambda x: x[0])]
91
+ new_query = copy_query(base_query)
92
+ new_query.select.selectItems = new_select_items
93
+ return new_query
94
+
95
+ def to_pure(self, config: FrameToPureConfig) -> str:
96
+ escaped_columns = []
97
+ for col_name in self.__column_name_list:
98
+ escaped_columns.append(escape_column_name(col_name))
99
+ return (f"{self.__base_frame.to_pure(config)}{config.separator(1)}" +
100
+ f"->select(~[{', '.join(escaped_columns)}])")
101
+
102
+ def base_frame(self) -> LegendQLApiBaseTdsFrame:
103
+ return self.__base_frame
104
+
105
+ def tds_frame_parameters(self) -> PyLegendList["LegendQLApiBaseTdsFrame"]:
106
+ return []
107
+
108
+ def calculate_columns(self) -> PyLegendSequence["TdsColumn"]:
109
+ base_columns = self.__base_frame.columns()
110
+ new_columns = []
111
+ for c in self.__column_name_list:
112
+ for base_col in base_columns:
113
+ if base_col.get_name() == c:
114
+ new_columns.append(base_col.copy())
115
+ break
116
+ return new_columns
117
+
118
+ def validate(self) -> bool:
119
+ base_columns = self.__base_frame.columns()
120
+ for c in self.__column_name_list:
121
+ found_col = False
122
+ for base_col in base_columns:
123
+ if base_col.get_name() == c:
124
+ found_col = True
125
+ break
126
+ if not found_col:
127
+ raise ValueError(
128
+ f"Column - '{c}' in select columns list doesn't exist in the current frame. "
129
+ f"Current frame columns: {[x.get_name() for x in base_columns]}"
130
+ )
131
+ return True
@@ -0,0 +1,82 @@
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
+ PyLegendList,
17
+ PyLegendSequence
18
+ )
19
+ from pylegend.core.tds.legendql_api.frames.legendql_api_applied_function_tds_frame import LegendQLApiAppliedFunction
20
+ from pylegend.core.tds.sql_query_helpers import copy_query, create_sub_query
21
+ from pylegend.core.sql.metamodel import (
22
+ QuerySpecification,
23
+ LongLiteral,
24
+ )
25
+ from pylegend.core.tds.tds_column import TdsColumn
26
+ from pylegend.core.tds.tds_frame import FrameToSqlConfig
27
+ from pylegend.core.tds.tds_frame import FrameToPureConfig
28
+ from pylegend.core.tds.legendql_api.frames.legendql_api_base_tds_frame import LegendQLApiBaseTdsFrame
29
+
30
+
31
+ __all__: PyLegendSequence[str] = [
32
+ "LegendQLApiSliceFunction"
33
+ ]
34
+
35
+
36
+ class LegendQLApiSliceFunction(LegendQLApiAppliedFunction):
37
+ __base_frame: LegendQLApiBaseTdsFrame
38
+ __start_row: int
39
+ __end_row: int
40
+
41
+ @classmethod
42
+ def name(cls) -> str:
43
+ return "slice"
44
+
45
+ def __init__(self, base_frame: LegendQLApiBaseTdsFrame, start_row: int, end_row: int) -> None:
46
+ self.__base_frame = base_frame
47
+ self.__start_row = start_row
48
+ self.__end_row = end_row
49
+
50
+ def to_sql(self, config: FrameToSqlConfig) -> QuerySpecification:
51
+ base_query = self.__base_frame.to_sql_query_object(config)
52
+ should_create_sub_query = (base_query.offset is not None) or (base_query.limit is not None)
53
+ new_query = (
54
+ create_sub_query(base_query, config, "root") if should_create_sub_query else
55
+ copy_query(base_query)
56
+ )
57
+ new_query.offset = LongLiteral(self.__start_row)
58
+ new_query.limit = LongLiteral(self.__end_row - self.__start_row)
59
+ return new_query
60
+
61
+ def to_pure(self, config: FrameToPureConfig) -> str:
62
+ return (f"{self.__base_frame.to_pure(config)}{config.separator(1)}"
63
+ f"->slice({self.__start_row}, {self.__end_row})")
64
+
65
+ def base_frame(self) -> LegendQLApiBaseTdsFrame:
66
+ return self.__base_frame
67
+
68
+ def tds_frame_parameters(self) -> PyLegendList["LegendQLApiBaseTdsFrame"]:
69
+ return []
70
+
71
+ def calculate_columns(self) -> PyLegendSequence["TdsColumn"]:
72
+ return [c.copy() for c in self.__base_frame.columns()]
73
+
74
+ def validate(self) -> bool:
75
+ if self.__start_row < 0:
76
+ raise ValueError(
77
+ "Start row argument of slice function cannot be negative. Start row: " + str(self.__start_row)
78
+ )
79
+ if self.__end_row <= self.__start_row:
80
+ raise ValueError("End row argument of slice function cannot be less than or equal to start row argument. "
81
+ f"Start row: {self.__start_row}, End row: {self.__end_row}")
82
+ return True
@@ -0,0 +1,93 @@
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
+ PyLegendList,
17
+ PyLegendSequence,
18
+ PyLegendCallable,
19
+ PyLegendUnion,
20
+ )
21
+ from pylegend.core.language.legendql_api.legendql_api_custom_expressions import (
22
+ LegendQLApiPrimitive,
23
+ LegendQLApiSortInfo,
24
+ )
25
+ from pylegend.core.language.legendql_api.legendql_api_tds_row import LegendQLApiTdsRow
26
+ from pylegend.core.sql.metamodel import (
27
+ QuerySpecification,
28
+ )
29
+ from pylegend.core.tds.legendql_api.frames.functions.legendql_api_function_helpers import infer_sorts_from_frame
30
+ from pylegend.core.tds.legendql_api.frames.legendql_api_applied_function_tds_frame import LegendQLApiAppliedFunction
31
+ from pylegend.core.tds.legendql_api.frames.legendql_api_base_tds_frame import LegendQLApiBaseTdsFrame
32
+ from pylegend.core.tds.sql_query_helpers import copy_query, create_sub_query
33
+ from pylegend.core.tds.tds_column import TdsColumn
34
+ from pylegend.core.tds.tds_frame import FrameToPureConfig
35
+ from pylegend.core.tds.tds_frame import FrameToSqlConfig
36
+
37
+ __all__: PyLegendSequence[str] = [
38
+ "LegendQLApiSortFunction"
39
+ ]
40
+
41
+
42
+ class LegendQLApiSortFunction(LegendQLApiAppliedFunction):
43
+ __base_frame: LegendQLApiBaseTdsFrame
44
+ __sort_infos: PyLegendList[LegendQLApiSortInfo]
45
+
46
+ @classmethod
47
+ def name(cls) -> str:
48
+ return "sort"
49
+
50
+ def __init__(
51
+ self,
52
+ base_frame: LegendQLApiBaseTdsFrame,
53
+ sort_infos: PyLegendUnion[
54
+ str,
55
+ PyLegendList[str],
56
+ PyLegendCallable[
57
+ [LegendQLApiTdsRow],
58
+ PyLegendUnion[
59
+ LegendQLApiPrimitive,
60
+ LegendQLApiSortInfo,
61
+ PyLegendList[PyLegendUnion[LegendQLApiPrimitive, LegendQLApiSortInfo]],
62
+ ]
63
+ ]
64
+ ]
65
+ ) -> None:
66
+ self.__base_frame = base_frame
67
+ self.__sort_infos = infer_sorts_from_frame(base_frame, sort_infos, "'sort' function sort_infos")
68
+
69
+ def to_sql(self, config: FrameToSqlConfig) -> QuerySpecification:
70
+ base_query = self.__base_frame.to_sql_query_object(config)
71
+ should_create_sub_query = (base_query.offset is not None) or (base_query.limit is not None)
72
+ new_query = (
73
+ create_sub_query(base_query, config, "root") if should_create_sub_query else
74
+ copy_query(base_query)
75
+ )
76
+ new_query.orderBy = [i.to_sql_node(query=new_query, config=config) for i in self.__sort_infos]
77
+ return new_query
78
+
79
+ def to_pure(self, config: FrameToPureConfig) -> str:
80
+ return (f"{self.__base_frame.to_pure(config)}{config.separator(1)}" +
81
+ f"->sort([{', '.join([i.to_pure_expression(config) for i in self.__sort_infos])}])")
82
+
83
+ def base_frame(self) -> LegendQLApiBaseTdsFrame:
84
+ return self.__base_frame
85
+
86
+ def tds_frame_parameters(self) -> PyLegendList["LegendQLApiBaseTdsFrame"]:
87
+ return []
88
+
89
+ def calculate_columns(self) -> PyLegendSequence["TdsColumn"]:
90
+ return [c.copy() for c in self.__base_frame.columns()]
91
+
92
+ def validate(self) -> bool:
93
+ return True