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,139 @@
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.sql.metamodel import (
21
+ QuerySpecification,
22
+ Union,
23
+ AliasedRelation,
24
+ SingleColumn,
25
+ Select,
26
+ QualifiedNameReference,
27
+ QualifiedName,
28
+ TableSubquery,
29
+ Query,
30
+ )
31
+ from pylegend.core.tds.sql_query_helpers import create_sub_query
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_tds_frame import LegendQLApiTdsFrame
36
+ from pylegend.core.tds.legendql_api.frames.legendql_api_base_tds_frame import LegendQLApiBaseTdsFrame
37
+
38
+
39
+ __all__: PyLegendSequence[str] = [
40
+ "LegendQLApiConcatenateFunction"
41
+ ]
42
+
43
+
44
+ class LegendQLApiConcatenateFunction(LegendQLApiAppliedFunction):
45
+ __base_frame: LegendQLApiBaseTdsFrame
46
+ __other_frame: LegendQLApiBaseTdsFrame
47
+
48
+ @classmethod
49
+ def name(cls) -> str:
50
+ return "concatenate"
51
+
52
+ def __init__(self, base_frame: LegendQLApiBaseTdsFrame, other: LegendQLApiTdsFrame) -> None:
53
+ self.__base_frame = base_frame
54
+ if not isinstance(other, LegendQLApiBaseTdsFrame):
55
+ raise ValueError("Expected LegendQLApiBaseTdsFrame") # pragma: no cover
56
+ self.__other_frame = other
57
+
58
+ def to_sql(self, config: FrameToSqlConfig) -> QuerySpecification:
59
+ base_query = self.__base_frame.to_sql_query_object(config)
60
+ other_query = self.__other_frame.to_sql_query_object(config)
61
+
62
+ db_extension = config.sql_to_string_generator().get_db_extension()
63
+ root_alias = db_extension.quote_identifier("root")
64
+ columns = [db_extension.quote_identifier(c.get_name()) for c in self.__base_frame.columns()]
65
+
66
+ return QuerySpecification(
67
+ select=Select(
68
+ selectItems=[
69
+ SingleColumn(
70
+ alias=x,
71
+ expression=QualifiedNameReference(name=QualifiedName(parts=[root_alias, x]))
72
+ )
73
+ for x in columns
74
+ ],
75
+ distinct=False
76
+ ),
77
+ from_=[
78
+ AliasedRelation(
79
+ relation=TableSubquery(
80
+ query=Query(
81
+ queryBody=Union(
82
+ left=create_sub_query(base_query, config, "left"),
83
+ right=create_sub_query(other_query, config, "right"),
84
+ distinct=False
85
+ ),
86
+ limit=None, offset=None, orderBy=[]
87
+ )
88
+ ),
89
+ alias=root_alias,
90
+ columnNames=columns
91
+ )
92
+ ],
93
+ where=None,
94
+ groupBy=[],
95
+ having=None,
96
+ orderBy=[],
97
+ limit=None,
98
+ offset=None
99
+ )
100
+
101
+ def to_pure(self, config: FrameToPureConfig) -> str:
102
+ return (f"{self.__base_frame.to_pure(config)}{config.separator(1)}"
103
+ f"->concatenate({config.separator(2)}"
104
+ f"{self.__other_frame.to_pure(config.push_indent(2))}"
105
+ f"{config.separator(1)})")
106
+
107
+ def base_frame(self) -> LegendQLApiBaseTdsFrame:
108
+ return self.__base_frame
109
+
110
+ def tds_frame_parameters(self) -> PyLegendList["LegendQLApiBaseTdsFrame"]:
111
+ return [self.__other_frame]
112
+
113
+ def calculate_columns(self) -> PyLegendSequence["TdsColumn"]:
114
+ return [c.copy() for c in self.__base_frame.columns()]
115
+
116
+ def validate(self) -> bool:
117
+ base_frame_cols = self.__base_frame.columns()
118
+ other_frame_cols = self.__other_frame.columns()
119
+
120
+ if len(base_frame_cols) != len(other_frame_cols):
121
+ cols1 = "[" + ", ".join([str(c) for c in base_frame_cols]) + "]"
122
+ cols2 = "[" + ", ".join([str(c) for c in other_frame_cols]) + "]"
123
+ raise ValueError(
124
+ "Cannot concatenate two Tds Frames with different column counts. \n"
125
+ f"Frame 1 cols - (Count: {len(base_frame_cols)}) - {cols1} \n"
126
+ f"Frame 2 cols - (Count: {len(other_frame_cols)}) - {cols2} \n"
127
+ )
128
+
129
+ for i in range(0, len(base_frame_cols)):
130
+ base_col = base_frame_cols[i]
131
+ other_col = other_frame_cols[i]
132
+
133
+ if (base_col.get_name() != other_col.get_name()) or (base_col.get_type() != other_col.get_type()):
134
+ raise ValueError(
135
+ f"Column name/type mismatch when concatenating Tds Frames at index {i}. "
136
+ f"Frame 1 column - {base_col}, Frame 2 column - {other_col}"
137
+ )
138
+
139
+ return True
@@ -0,0 +1,69 @@
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
+ )
24
+ from pylegend.core.tds.tds_column import TdsColumn
25
+ from pylegend.core.tds.tds_frame import FrameToSqlConfig
26
+ from pylegend.core.tds.tds_frame import FrameToPureConfig
27
+ from pylegend.core.tds.legendql_api.frames.legendql_api_base_tds_frame import LegendQLApiBaseTdsFrame
28
+
29
+
30
+ __all__: PyLegendSequence[str] = [
31
+ "LegendQLApiDistinctFunction"
32
+ ]
33
+
34
+
35
+ class LegendQLApiDistinctFunction(LegendQLApiAppliedFunction):
36
+ __base_frame: LegendQLApiBaseTdsFrame
37
+
38
+ @classmethod
39
+ def name(cls) -> str:
40
+ return "distinct"
41
+
42
+ def __init__(self, base_frame: LegendQLApiBaseTdsFrame) -> None:
43
+ self.__base_frame = base_frame
44
+
45
+ def to_sql(self, config: FrameToSqlConfig) -> QuerySpecification:
46
+ base_query = self.__base_frame.to_sql_query_object(config)
47
+ should_create_sub_query = (base_query.offset is not None) or (base_query.limit is not None)
48
+ new_query = (
49
+ create_sub_query(base_query, config, "root") if should_create_sub_query else
50
+ copy_query(base_query)
51
+ )
52
+ new_query.select.distinct = True
53
+ return new_query
54
+
55
+ def to_pure(self, config: FrameToPureConfig) -> str:
56
+ return (f"{self.__base_frame.to_pure(config)}{config.separator(1)}"
57
+ f"->distinct()")
58
+
59
+ def base_frame(self) -> LegendQLApiBaseTdsFrame:
60
+ return self.__base_frame
61
+
62
+ def tds_frame_parameters(self) -> PyLegendList[LegendQLApiBaseTdsFrame]:
63
+ return []
64
+
65
+ def calculate_columns(self) -> PyLegendSequence[TdsColumn]:
66
+ return [c.copy() for c in self.__base_frame.columns()]
67
+
68
+ def validate(self) -> bool:
69
+ return True
@@ -0,0 +1,74 @@
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
+ "LegendQLApiDropFunction"
33
+ ]
34
+
35
+
36
+ class LegendQLApiDropFunction(LegendQLApiAppliedFunction):
37
+ __base_frame: LegendQLApiBaseTdsFrame
38
+ __row_count: int
39
+
40
+ @classmethod
41
+ def name(cls) -> str:
42
+ return "drop"
43
+
44
+ def __init__(self, base_frame: LegendQLApiBaseTdsFrame, row_count: int) -> None:
45
+ self.__base_frame = base_frame
46
+ self.__row_count = row_count
47
+
48
+ def to_sql(self, config: FrameToSqlConfig) -> QuerySpecification:
49
+ base_query = self.__base_frame.to_sql_query_object(config)
50
+ should_create_sub_query = (base_query.offset is not None) or (base_query.limit is not None)
51
+ new_query = (
52
+ create_sub_query(base_query, config, "root") if should_create_sub_query else
53
+ copy_query(base_query)
54
+ )
55
+ new_query.offset = LongLiteral(value=self.__row_count)
56
+ return new_query
57
+
58
+ def to_pure(self, config: FrameToPureConfig) -> str:
59
+ return (f"{self.__base_frame.to_pure(config)}{config.separator(1)}"
60
+ f"->drop({self.__row_count})")
61
+
62
+ def base_frame(self) -> LegendQLApiBaseTdsFrame:
63
+ return self.__base_frame
64
+
65
+ def tds_frame_parameters(self) -> PyLegendList["LegendQLApiBaseTdsFrame"]:
66
+ return []
67
+
68
+ def calculate_columns(self) -> PyLegendSequence["TdsColumn"]:
69
+ return [c.copy() for c in self.__base_frame.columns()]
70
+
71
+ def validate(self) -> bool:
72
+ if self.__row_count < 0:
73
+ raise ValueError("Row count argument of drop function cannot be negative")
74
+ return True
@@ -0,0 +1,256 @@
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.sql.metamodel_extension import WindowExpression
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
+ Window,
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 import (
37
+ PyLegendPrimitive,
38
+ PyLegendPrimitiveOrPythonPrimitive,
39
+ convert_literal_to_literal_expression, PyLegendPrimitiveCollection, create_primitive_collection,
40
+ )
41
+ from pylegend.core.language.shared.helpers import generate_pure_lambda, escape_column_name
42
+ from pylegend.core.tds.abstract.function_helpers import tds_column_for_primitive
43
+
44
+ __all__: PyLegendSequence[str] = [
45
+ "LegendQLApiExtendFunction"
46
+ ]
47
+
48
+
49
+ class LegendQLApiExtendFunction(LegendQLApiAppliedFunction):
50
+ __base_frame: LegendQLApiBaseTdsFrame
51
+ __new_column_expressions: PyLegendList[
52
+ PyLegendUnion[
53
+ PyLegendTuple[str, PyLegendPrimitiveOrPythonPrimitive],
54
+ PyLegendTuple[str, PyLegendPrimitiveOrPythonPrimitive, PyLegendPrimitive]
55
+ ]
56
+ ]
57
+
58
+ @classmethod
59
+ def name(cls) -> str:
60
+ return "extend"
61
+
62
+ def __init__(
63
+ self,
64
+ base_frame: LegendQLApiBaseTdsFrame,
65
+ extend_columns: PyLegendUnion[
66
+ PyLegendTuple[
67
+ str,
68
+ PyLegendCallable[[LegendQLApiTdsRow], PyLegendPrimitiveOrPythonPrimitive]
69
+ ],
70
+ PyLegendTuple[
71
+ str,
72
+ PyLegendCallable[[LegendQLApiTdsRow], PyLegendPrimitiveOrPythonPrimitive],
73
+ PyLegendCallable[[PyLegendPrimitiveCollection], PyLegendPrimitive]
74
+ ],
75
+ PyLegendList[
76
+ PyLegendUnion[
77
+ PyLegendTuple[
78
+ str,
79
+ PyLegendCallable[[LegendQLApiTdsRow], PyLegendPrimitiveOrPythonPrimitive]
80
+ ],
81
+ PyLegendTuple[
82
+ str,
83
+ PyLegendCallable[[LegendQLApiTdsRow], PyLegendPrimitiveOrPythonPrimitive],
84
+ PyLegendCallable[[PyLegendPrimitiveCollection], PyLegendPrimitive]
85
+ ]
86
+ ]
87
+ ]
88
+ ]
89
+ ) -> None:
90
+ self.__base_frame = base_frame
91
+ col_expressions: PyLegendList[
92
+ PyLegendUnion[
93
+ PyLegendTuple[str, PyLegendPrimitiveOrPythonPrimitive],
94
+ PyLegendTuple[str, PyLegendPrimitiveOrPythonPrimitive, PyLegendPrimitive]
95
+ ]
96
+ ] = []
97
+ tds_row = LegendQLApiTdsRow.from_tds_frame("r", self.__base_frame)
98
+ for (i, extend_column) in enumerate(extend_columns if isinstance(extend_columns, list) else [extend_columns]):
99
+ if isinstance(extend_column, tuple) and len(extend_column) in [2, 3]:
100
+ if not isinstance(extend_column[0], str):
101
+ raise TypeError(
102
+ "'extend' function extend_columns argument incompatible. "
103
+ "First element in an extend tuple should be a string (new column name). "
104
+ "E.g - ('new col', lambda r: r.c1 + 1). "
105
+ f"Element at index {i} (0-indexed) is incompatible"
106
+ )
107
+ if not isinstance(extend_column[1], type(lambda x: 0)) or (extend_column[1].__code__.co_argcount != 1):
108
+ raise TypeError(
109
+ "'extend' function extend_columns argument incompatible. "
110
+ "Second element in an extend tuple should be a lambda function which takes one argument "
111
+ "(LegendQLApiTdsRow) E.g - ('new col', lambda r: r.c1 + 1)."
112
+ f"Element at index {i} (0-indexed) is incompatible"
113
+ )
114
+ try:
115
+ result = extend_column[1](tds_row)
116
+ except Exception as e:
117
+ raise RuntimeError(
118
+ "'extend' function extend_columns argument incompatible. "
119
+ f"Error occurred while evaluating extend lambda at index {i} (0-indexed). Message: " + str(e)
120
+ ) from e
121
+
122
+ if not isinstance(result, (int, float, bool, str, date, datetime, PyLegendPrimitive)):
123
+ raise TypeError(
124
+ "'extend' function extend_columns argument incompatible. "
125
+ f"Extend lambda at index {i} (0-indexed) returns non-primitive - {str(type(result))}"
126
+ )
127
+ if len(extend_column) == 2:
128
+ col_expressions.append((extend_column[0], result))
129
+ else:
130
+ if (not isinstance(extend_column[2], type(lambda x: 0)) or
131
+ (extend_column[2].__code__.co_argcount != 1)):
132
+ raise TypeError(
133
+ "'extend' function extend_columns argument incompatible. "
134
+ "Third element in an extend tuple should be a lambda function which takes one argument "
135
+ "(collection) E.g - ('new col', lambda r: r.c1, lambda c: c.sum()). "
136
+ f"Element at index {i} (0-indexed) is incompatible"
137
+ )
138
+ collection = create_primitive_collection(result)
139
+ try:
140
+ agg_result = extend_column[2](collection)
141
+ except Exception as e:
142
+ raise RuntimeError(
143
+ "'extend' function extend_columns argument incompatible. "
144
+ f"Error occurred while evaluating aggregation lambda at index {i} (0-indexed). "
145
+ "Message: " + str(e)
146
+ ) from e
147
+ if not isinstance(agg_result, PyLegendPrimitive):
148
+ raise TypeError(
149
+ "'extend' function extend_columns argument incompatible. "
150
+ f"Aggregation lambda at index {i} (0-indexed) "
151
+ f"returns non-primitive - {str(type(agg_result))}"
152
+ )
153
+ col_expressions.append((extend_column[0], result, agg_result))
154
+ else:
155
+ raise TypeError(
156
+ "'extend' function extend_columns argument should be a list of tuples with two/three elements - "
157
+ "first element being a string (new column name), second element being a lambda "
158
+ "function which takes one argument (LegendQLApiTdsRow) and third element being an optional "
159
+ "aggregation lambda function which takes one argument "
160
+ "E.g - [('new col1', lambda r: r.c1 + 1), ('new col2', lambda r: r.c2, lambda c: c.sum())]. "
161
+ f"Element at index {i} (0-indexed) is incompatible"
162
+ )
163
+ self.__new_column_expressions = col_expressions
164
+
165
+ def to_sql(self, config: FrameToSqlConfig) -> QuerySpecification:
166
+ base_query = self.__base_frame.to_sql_query_object(config)
167
+ should_create_sub_query = len(base_query.groupBy) > 0
168
+ db_extension = config.sql_to_string_generator().get_db_extension()
169
+
170
+ new_query = (
171
+ create_sub_query(base_query, config, "root") if should_create_sub_query else
172
+ copy_query(base_query)
173
+ )
174
+
175
+ for c in self.__new_column_expressions:
176
+ if len(c) == 2:
177
+ if isinstance(c[1], (bool, int, float, str, date, datetime)):
178
+ col_sql_expr = convert_literal_to_literal_expression(c[1]).to_sql_expression(
179
+ {"r": new_query},
180
+ config
181
+ )
182
+ else:
183
+ col_sql_expr = c[1].to_sql_expression({"r": new_query}, config)
184
+
185
+ new_query.select.selectItems.append(
186
+ SingleColumn(alias=db_extension.quote_identifier(c[0]), expression=col_sql_expr)
187
+ )
188
+ else:
189
+ agg_sql_expr = c[2].to_sql_expression({"r": new_query}, config)
190
+ window_expr = WindowExpression(
191
+ nested=agg_sql_expr,
192
+ window=Window(windowRef=None, partitions=[], orderBy=[], windowFrame=None)
193
+ )
194
+ new_query.select.selectItems.append(
195
+ SingleColumn(alias=db_extension.quote_identifier(c[0]), expression=window_expr)
196
+ )
197
+ return new_query
198
+
199
+ def to_pure(self, config: FrameToPureConfig) -> str:
200
+ def render_single_column_expression(
201
+ c: PyLegendUnion[
202
+ PyLegendTuple[str, PyLegendPrimitiveOrPythonPrimitive],
203
+ PyLegendTuple[str, PyLegendPrimitiveOrPythonPrimitive, PyLegendPrimitive]
204
+ ]
205
+ ) -> str:
206
+ escaped_col_name = escape_column_name(c[0])
207
+ expr_str = (c[1].to_pure_expression(config) if isinstance(c[1], PyLegendPrimitive) else
208
+ convert_literal_to_literal_expression(c[1]).to_pure_expression(config))
209
+ if len(c) == 2:
210
+ return f"{escaped_col_name}:{generate_pure_lambda('r', expr_str)}"
211
+ else:
212
+ agg_expr_str = c[2].to_pure_expression(config).replace(expr_str, "$c")
213
+ return (f"{escaped_col_name}:"
214
+ f"{generate_pure_lambda('r', expr_str)}:"
215
+ f"{generate_pure_lambda('c', agg_expr_str)}")
216
+
217
+ if all([len(t) == 2 for t in self.__new_column_expressions]) or all(
218
+ [len(t) == 3 for t in self.__new_column_expressions]):
219
+ if len(self.__new_column_expressions) == 1:
220
+ extend_str = f"->extend(~{render_single_column_expression(self.__new_column_expressions[0])})"
221
+ else:
222
+ extend_str = (f"->extend(~[{config.separator(2)}" +
223
+ ("," + config.separator(2, True)).join(
224
+ [render_single_column_expression(x) for x in self.__new_column_expressions]
225
+ ) +
226
+ f"{config.separator(1)}])")
227
+ return f"{self.__base_frame.to_pure(config)}{config.separator(1)}" + extend_str
228
+ else:
229
+ extend_str = self.__base_frame.to_pure(config)
230
+ for c in self.__new_column_expressions:
231
+ extend_str += f"{config.separator(1)}->extend(~{render_single_column_expression(c)})"
232
+ return extend_str
233
+
234
+ def base_frame(self) -> LegendQLApiBaseTdsFrame:
235
+ return self.__base_frame
236
+
237
+ def tds_frame_parameters(self) -> PyLegendList["LegendQLApiBaseTdsFrame"]:
238
+ return []
239
+
240
+ def calculate_columns(self) -> PyLegendSequence["TdsColumn"]:
241
+ new_columns = [c.copy() for c in self.__base_frame.columns()]
242
+ for c in self.__new_column_expressions:
243
+ new_columns.append(tds_column_for_primitive(c[0], c[1] if len(c) == 2 else c[2]))
244
+ return new_columns
245
+
246
+ def validate(self) -> bool:
247
+ col_names = [x[0] for x in self.__new_column_expressions]
248
+
249
+ if len(col_names) != len(set(col_names)):
250
+ raise ValueError(f"Extend column names list has duplicates: {col_names}")
251
+
252
+ for c in col_names:
253
+ if c in [c.get_name() for c in self.__base_frame.columns()]:
254
+ raise ValueError(f"Extend column name - '{c}' already exists in base frame")
255
+
256
+ return True
@@ -0,0 +1,121 @@
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_tds_row import LegendQLApiTdsRow
22
+ from pylegend.core.tds.legendql_api.frames.legendql_api_applied_function_tds_frame import LegendQLApiAppliedFunction
23
+ from pylegend.core.tds.sql_query_helpers import copy_query, create_sub_query
24
+ from pylegend.core.sql.metamodel import (
25
+ QuerySpecification,
26
+ LogicalBinaryType,
27
+ LogicalBinaryExpression,
28
+ )
29
+ from pylegend.core.tds.tds_column import TdsColumn
30
+ from pylegend.core.tds.tds_frame import FrameToSqlConfig
31
+ from pylegend.core.tds.tds_frame import FrameToPureConfig
32
+ from pylegend.core.tds.legendql_api.frames.legendql_api_base_tds_frame import LegendQLApiBaseTdsFrame
33
+ from pylegend.core.language import (
34
+ PyLegendBoolean,
35
+ PyLegendBooleanLiteralExpression,
36
+ PyLegendPrimitive,
37
+ convert_literal_to_literal_expression,
38
+ )
39
+ from pylegend.core.language.shared.helpers import generate_pure_lambda
40
+
41
+ __all__: PyLegendSequence[str] = [
42
+ "LegendQLApiFilterFunction"
43
+ ]
44
+
45
+
46
+ class LegendQLApiFilterFunction(LegendQLApiAppliedFunction):
47
+ __base_frame: LegendQLApiBaseTdsFrame
48
+ __filter_function: PyLegendCallable[[LegendQLApiTdsRow], PyLegendUnion[bool, PyLegendBoolean]]
49
+
50
+ @classmethod
51
+ def name(cls) -> str:
52
+ return "filter"
53
+
54
+ def __init__(
55
+ self,
56
+ base_frame: LegendQLApiBaseTdsFrame,
57
+ filter_function: PyLegendCallable[[LegendQLApiTdsRow], PyLegendUnion[bool, PyLegendBoolean]]
58
+ ) -> None:
59
+ self.__base_frame = base_frame
60
+ self.__filter_function = filter_function
61
+
62
+ def to_sql(self, config: FrameToSqlConfig) -> QuerySpecification:
63
+ base_query = self.__base_frame.to_sql_query_object(config)
64
+ should_create_sub_query = (len(base_query.groupBy) > 0) or \
65
+ (base_query.offset is not None) or (base_query.limit is not None)
66
+ new_query = (
67
+ create_sub_query(base_query, config, "root") if should_create_sub_query else
68
+ copy_query(base_query)
69
+ )
70
+
71
+ tds_row = LegendQLApiTdsRow.from_tds_frame("frame", self.__base_frame)
72
+ filter_expr = self.__filter_function(tds_row)
73
+ if isinstance(filter_expr, bool):
74
+ filter_expr = PyLegendBoolean(PyLegendBooleanLiteralExpression(filter_expr))
75
+ filter_sql_expr = filter_expr.to_sql_expression(
76
+ {"frame": new_query},
77
+ config
78
+ )
79
+
80
+ if new_query.where is None:
81
+ new_query.where = filter_sql_expr
82
+ else:
83
+ new_query.where = LogicalBinaryExpression(LogicalBinaryType.AND, new_query.where, filter_sql_expr)
84
+
85
+ return new_query
86
+
87
+ def to_pure(self, config: FrameToPureConfig) -> str:
88
+ tds_row = LegendQLApiTdsRow.from_tds_frame("r", self.__base_frame)
89
+ filter_expr = self.__filter_function(tds_row)
90
+ filter_expr_string = (filter_expr.to_pure_expression(config) if isinstance(filter_expr, PyLegendPrimitive) else
91
+ convert_literal_to_literal_expression(filter_expr).to_pure_expression(config))
92
+ return (f"{self.__base_frame.to_pure(config)}{config.separator(1)}" +
93
+ f"->filter({generate_pure_lambda('r', filter_expr_string)})")
94
+
95
+ def base_frame(self) -> LegendQLApiBaseTdsFrame:
96
+ return self.__base_frame
97
+
98
+ def tds_frame_parameters(self) -> PyLegendList["LegendQLApiBaseTdsFrame"]:
99
+ return []
100
+
101
+ def calculate_columns(self) -> PyLegendSequence["TdsColumn"]:
102
+ return [c.copy() for c in self.__base_frame.columns()]
103
+
104
+ def validate(self) -> bool:
105
+ tds_row = LegendQLApiTdsRow.from_tds_frame("frame", self.__base_frame)
106
+
107
+ copy = self.__filter_function # For MyPy
108
+ if not isinstance(copy, type(lambda x: 0)) or (copy.__code__.co_argcount != 1):
109
+ raise TypeError("Filter function should be a lambda which takes one argument (TDSRow)")
110
+
111
+ try:
112
+ result = self.__filter_function(tds_row)
113
+ except Exception as e:
114
+ raise RuntimeError(
115
+ "Filter function incompatible. Error occurred while evaluating. Message: " + str(e)
116
+ ) from e
117
+
118
+ if not isinstance(result, (bool, PyLegendBoolean)):
119
+ raise RuntimeError("Filter function incompatible. Returns non boolean - " + str(type(result)))
120
+
121
+ return True