pylegend 0.10.0__py3-none-any.whl → 0.12.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 (31) hide show
  1. pylegend/core/database/sql_to_string/db_extension.py +68 -6
  2. pylegend/core/language/legendql_api/legendql_api_custom_expressions.py +190 -5
  3. pylegend/core/language/pandas_api/pandas_api_series.py +3 -0
  4. pylegend/core/sql/metamodel.py +4 -1
  5. pylegend/core/tds/legendql_api/frames/functions/legendql_api_distinct_function.py +53 -7
  6. pylegend/core/tds/legendql_api/frames/legendql_api_base_tds_frame.py +146 -4
  7. pylegend/core/tds/legendql_api/frames/legendql_api_tds_frame.py +33 -2
  8. pylegend/core/tds/pandas_api/frames/functions/aggregate_function.py +221 -96
  9. pylegend/core/tds/pandas_api/frames/functions/assign_function.py +65 -23
  10. pylegend/core/tds/pandas_api/frames/functions/drop.py +3 -3
  11. pylegend/core/tds/pandas_api/frames/functions/dropna.py +167 -0
  12. pylegend/core/tds/pandas_api/frames/functions/fillna.py +162 -0
  13. pylegend/core/tds/pandas_api/frames/functions/filter.py +10 -5
  14. pylegend/core/tds/pandas_api/frames/functions/merge.py +513 -0
  15. pylegend/core/tds/pandas_api/frames/functions/rename.py +214 -0
  16. pylegend/core/tds/pandas_api/frames/functions/truncate_function.py +151 -120
  17. pylegend/core/tds/pandas_api/frames/pandas_api_applied_function_tds_frame.py +7 -3
  18. pylegend/core/tds/pandas_api/frames/pandas_api_base_tds_frame.py +559 -18
  19. pylegend/core/tds/pandas_api/frames/pandas_api_groupby_tds_frame.py +325 -0
  20. pylegend/core/tds/pandas_api/frames/pandas_api_tds_frame.py +218 -12
  21. pylegend/extensions/tds/abstract/csv_tds_frame.py +95 -0
  22. pylegend/extensions/tds/legendql_api/frames/legendql_api_csv_input_frame.py +36 -0
  23. pylegend/extensions/tds/pandas_api/frames/pandas_api_legend_function_input_frame.py +9 -4
  24. pylegend/extensions/tds/pandas_api/frames/pandas_api_legend_service_input_frame.py +12 -5
  25. pylegend/extensions/tds/pandas_api/frames/pandas_api_table_spec_input_frame.py +12 -4
  26. {pylegend-0.10.0.dist-info → pylegend-0.12.0.dist-info}/METADATA +1 -1
  27. {pylegend-0.10.0.dist-info → pylegend-0.12.0.dist-info}/RECORD +31 -24
  28. {pylegend-0.10.0.dist-info → pylegend-0.12.0.dist-info}/WHEEL +0 -0
  29. {pylegend-0.10.0.dist-info → pylegend-0.12.0.dist-info}/licenses/LICENSE +0 -0
  30. {pylegend-0.10.0.dist-info → pylegend-0.12.0.dist-info}/licenses/LICENSE.spdx +0 -0
  31. {pylegend-0.10.0.dist-info → pylegend-0.12.0.dist-info}/licenses/NOTICE +0 -0
@@ -0,0 +1,214 @@
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
+ PyLegendUnion,
19
+ PyLegendOptional,
20
+ PyLegendCallable,
21
+ PyLegendDict
22
+ )
23
+ from pylegend.core.language import (
24
+ PyLegendInteger,
25
+ PyLegendBoolean
26
+ )
27
+ from pylegend.core.sql.metamodel import (
28
+ QuerySpecification,
29
+ SelectItem,
30
+ SingleColumn
31
+ )
32
+ from pylegend.core.tds.pandas_api.frames.pandas_api_applied_function_tds_frame import PandasApiAppliedFunction
33
+ from pylegend.core.tds.pandas_api.frames.pandas_api_base_tds_frame import PandasApiBaseTdsFrame
34
+ from pylegend.core.tds.sql_query_helpers import copy_query
35
+ from pylegend.core.tds.tds_column import TdsColumn
36
+ from pylegend.core.tds.tds_frame import FrameToPureConfig, FrameToSqlConfig
37
+
38
+
39
+ class PandasApiRenameFunction(PandasApiAppliedFunction):
40
+ __base_frame: PandasApiBaseTdsFrame
41
+ __mapper: PyLegendOptional[PyLegendUnion[PyLegendDict[str, str], PyLegendCallable[[str], str]]]
42
+ __axis: PyLegendUnion[str, int, PyLegendInteger]
43
+ __index: PyLegendOptional[PyLegendUnion[PyLegendDict[str, str], PyLegendCallable[[str], str]]]
44
+ __columns: PyLegendOptional[PyLegendUnion[PyLegendDict[str, str], PyLegendCallable[[str], str]]]
45
+ __level: PyLegendOptional[PyLegendUnion[int, PyLegendInteger, str]]
46
+ __inplace: PyLegendUnion[bool, PyLegendBoolean]
47
+ __copy: PyLegendUnion[bool, PyLegendBoolean]
48
+ __errors: str
49
+
50
+ @classmethod
51
+ def name(cls) -> str:
52
+ return "rename" # pragma: no cover
53
+
54
+ def __init__(
55
+ self,
56
+ base_frame: PandasApiBaseTdsFrame,
57
+ mapper: PyLegendOptional[PyLegendUnion[PyLegendDict[str, str], PyLegendCallable[[str], str]]],
58
+ axis: PyLegendUnion[str, int, PyLegendInteger],
59
+ index: PyLegendOptional[PyLegendUnion[PyLegendDict[str, str], PyLegendCallable[[str], str]]],
60
+ columns: PyLegendOptional[PyLegendUnion[PyLegendDict[str, str], PyLegendCallable[[str], str]]],
61
+ level: PyLegendOptional[PyLegendUnion[int, PyLegendInteger, str]],
62
+ inplace: PyLegendUnion[bool, PyLegendBoolean],
63
+ errors: str,
64
+ copy: PyLegendUnion[bool, PyLegendBoolean]
65
+ ) -> None:
66
+ self.__base_frame = base_frame
67
+ self.__mapper = mapper
68
+ self.__axis = axis
69
+ self.__index = index
70
+ self.__columns = columns
71
+ self.__level = level
72
+ self.__inplace = inplace
73
+ self.__errors = errors
74
+ self.__copy = copy
75
+
76
+ def __resolve_columns_mapping(self) -> PyLegendDict[str, str]:
77
+ base_cols = [c.get_name() for c in self.__base_frame.columns()]
78
+ mapping_source = None
79
+
80
+ axis_is_columns = (self.__axis == 1 or self.__axis == "columns")
81
+
82
+ # Priority: explicit columns=, else mapper when axis targets columns
83
+ if self.__columns is not None:
84
+ mapping_source = self.__columns
85
+ elif self.__mapper is not None and axis_is_columns:
86
+ mapping_source = self.__mapper
87
+
88
+ if mapping_source is None:
89
+ return {}
90
+
91
+ if not callable(mapping_source) and not isinstance(mapping_source, dict):
92
+ raise TypeError(
93
+ f"Rename mapping must be a dict or a callable, got {type(mapping_source)}"
94
+ )
95
+
96
+ out: PyLegendDict[str, str] = {}
97
+ if callable(mapping_source):
98
+ func = mapping_source
99
+ for col in base_cols:
100
+ new = func(col)
101
+ if not isinstance(new, str):
102
+ raise TypeError(
103
+ f"Rename function must return str, got {type(new)} for column {col}") # pragma: no cover
104
+ if new != col:
105
+ out[col] = new
106
+ else:
107
+ # dict-like
108
+ dict_map: PyLegendDict[str, str] = mapping_source
109
+ if self.__errors == "raise":
110
+ missing = [k for k in dict_map.keys() if k not in base_cols]
111
+ if missing:
112
+ raise KeyError(f"{missing} not found in axis")
113
+
114
+ for k, v in dict_map.items():
115
+ if k in base_cols and k != v:
116
+ out[k] = v
117
+
118
+ return out
119
+
120
+ def to_sql(self, config: FrameToSqlConfig) -> QuerySpecification:
121
+ rename_map = self.__resolve_columns_mapping()
122
+ base_query = self.__base_frame.to_sql_query_object(config)
123
+ db_extension = config.sql_to_string_generator().get_db_extension()
124
+
125
+ # Prepare quoted lookup for aliases
126
+ quoted_from = [db_extension.quote_identifier(s) for s in rename_map.keys()]
127
+ quoted_to = [db_extension.quote_identifier(rename_map[s]) for s in rename_map.keys()]
128
+
129
+ new_select_items: PyLegendList[SelectItem] = []
130
+ for col in base_query.select.selectItems:
131
+ if not isinstance(col, SingleColumn):
132
+ raise ValueError("Rename operation not supported for non-SingleColumn select items") # pragma: no cover
133
+ if col.alias is None:
134
+ raise ValueError("Rename operation requires SingleColumn items with aliases") # pragma: no cover
135
+ if col.alias in quoted_from:
136
+ new_alias = quoted_to[quoted_from.index(col.alias)]
137
+ new_select_items.append(SingleColumn(alias=new_alias, expression=col.expression))
138
+ else:
139
+ new_select_items.append(col)
140
+
141
+ new_query = copy_query(base_query)
142
+ new_query.select.selectItems = new_select_items
143
+ return new_query
144
+
145
+ def to_pure(self, config: FrameToPureConfig) -> str:
146
+ rename_map = self.__resolve_columns_mapping()
147
+ base_pure = self.__base_frame.to_pure(config)
148
+
149
+ # Build a single project that aliases columns to new names
150
+ project_items: PyLegendList[str] = []
151
+ for c in self.__base_frame.columns():
152
+ orig = c.get_name()
153
+ new = rename_map.get(orig, orig)
154
+ project_items.append(f"{new}:x|$x.{orig}")
155
+
156
+ project_body = ", ".join(project_items)
157
+ return (
158
+ f"{base_pure}{config.separator(1)}"
159
+ f"->project({config.separator(2)}~[{project_body}]{config.separator(1)})"
160
+ )
161
+
162
+ def base_frame(self) -> PandasApiBaseTdsFrame:
163
+ return self.__base_frame
164
+
165
+ def tds_frame_parameters(self) -> PyLegendList["PandasApiBaseTdsFrame"]:
166
+ return []
167
+
168
+ def calculate_columns(self) -> PyLegendSequence["TdsColumn"]:
169
+ rename_map = self.__resolve_columns_mapping()
170
+ new_cols = []
171
+ for c in self.__base_frame.columns():
172
+ name = c.get_name()
173
+ if name in rename_map:
174
+ new_cols.append(c.copy_with_changed_name(rename_map[name]))
175
+ else:
176
+ new_cols.append(c.copy())
177
+ names = [c.get_name() for c in new_cols]
178
+ if len(names) != len(set(names)):
179
+ raise ValueError("Resulting columns contain duplicates after rename")
180
+ return new_cols
181
+
182
+ def validate(self) -> bool:
183
+ if self.__level is not None:
184
+ raise NotImplementedError("level parameter not supported yet in Pandas API")
185
+
186
+ if not isinstance(self.__inplace, bool):
187
+ raise TypeError(f"inplace must be bool. Got {type(self.__inplace)}")
188
+ if self.__inplace is True:
189
+ raise NotImplementedError("inplace=True not supported yet in Pandas API")
190
+
191
+ if not isinstance(self.__copy, bool):
192
+ raise TypeError(f"copy must be bool. Got {type(self.__copy)}")
193
+ if self.__copy is False:
194
+ raise NotImplementedError("copy=False not supported yet in Pandas API")
195
+
196
+ if self.__errors not in ("ignore", "raise"):
197
+ raise ValueError(f"errors must be 'ignore' or 'raise'. Got {self.__errors}")
198
+
199
+ # axis validation
200
+ if self.__axis not in (1, "columns", 0, "index"):
201
+ raise ValueError(f"Unsupported axis {self.__axis}")
202
+ if self.__axis in (0, "index"):
203
+ raise NotImplementedError("Renaming index not supported yet in Pandas API")
204
+
205
+ # index
206
+ if self.__index is not None:
207
+ raise NotImplementedError("Index mapper not supported yet in Pandas API")
208
+
209
+ # conflict validation
210
+ if self.__mapper and self.__columns:
211
+ raise ValueError("Cannot specify both 'axis' and any of 'index' or 'columns'")
212
+
213
+ self.__resolve_columns_mapping() # runs validation
214
+ return True
@@ -1,120 +1,151 @@
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
-
16
- from datetime import date
17
- from pylegend._typing import (
18
- PyLegendList,
19
- PyLegendSequence,
20
- PyLegendUnion,
21
- )
22
- from pylegend.core.sql.metamodel import LongLiteral, QuerySpecification
23
- from pylegend.core.tds.pandas_api.frames.pandas_api_applied_function_tds_frame import PandasApiAppliedFunction
24
- from pylegend.core.tds.pandas_api.frames.pandas_api_base_tds_frame import PandasApiBaseTdsFrame
25
- from pylegend.core.tds.sql_query_helpers import create_sub_query, copy_query
26
- from pylegend.core.tds.tds_column import TdsColumn
27
- from pylegend.core.tds.tds_frame import FrameToPureConfig, FrameToSqlConfig
28
-
29
-
30
- class TruncateFunction(PandasApiAppliedFunction):
31
- __base_frame: PandasApiBaseTdsFrame
32
- __before: int
33
- __after: PyLegendUnion[int, None]
34
- __axis: PyLegendUnion[str, int]
35
- __copy: bool
36
-
37
- @classmethod
38
- def name(cls) -> str:
39
- return "truncate" # pragma: no cover
40
-
41
- def __init__(
42
- self,
43
- base_frame: PandasApiBaseTdsFrame,
44
- before: PyLegendUnion[date, str, int, None],
45
- after: PyLegendUnion[date, str, int, None],
46
- axis: PyLegendUnion[str, int],
47
- copy: bool,
48
- ) -> None:
49
- self.__base_frame = base_frame
50
- self.__before_input = before
51
- self.__after_input = after
52
- self.__axis = axis
53
- self.__copy = copy
54
-
55
- def to_sql(self, config: FrameToSqlConfig) -> QuerySpecification:
56
- base_query: QuerySpecification = self.__base_frame.to_sql_query_object(config)
57
- should_create_sub_query = (base_query.offset is not None) or (base_query.limit is not None)
58
- new_query = create_sub_query(base_query, config, "root") if should_create_sub_query else copy_query(base_query)
59
- new_query.offset = LongLiteral(self.__before)
60
- if self.__after is not None:
61
- new_query.limit = LongLiteral(self.__after - self.__before + 1)
62
- return new_query
63
-
64
- def to_pure(self, config: FrameToPureConfig) -> str:
65
- if self.__after is None:
66
- return f"{self.__base_frame.to_pure(config)}{config.separator(1)}" f"->drop({self.__before})"
67
-
68
- start_row = self.__before
69
- end_row = self.__after + 1
70
- return f"{self.__base_frame.to_pure(config)}{config.separator(1)}" f"->slice({start_row}, {end_row})"
71
-
72
- def base_frame(self) -> PandasApiBaseTdsFrame:
73
- return self.__base_frame
74
-
75
- def tds_frame_parameters(self) -> PyLegendList["PandasApiBaseTdsFrame"]:
76
- return []
77
-
78
- def calculate_columns(self) -> PyLegendSequence["TdsColumn"]:
79
- return [c.copy() for c in self.__base_frame.columns()]
80
-
81
- def validate(self) -> bool:
82
- if self.__axis not in [0, "index"]:
83
- raise NotImplementedError(
84
- f"The 'axis' parameter of the truncate function must be 0 or 'index', but got: {self.__axis}"
85
- )
86
-
87
- if self.__copy not in [True]:
88
- raise NotImplementedError(f"The 'copy' parameter of the truncate function must be True, but got: {self.__copy}")
89
-
90
- if self.__before_input is None:
91
- self.__before = 0
92
- else:
93
- self.__before = self.get_positive_integer_or_raise_exception(self.__before_input, variable_name="before")
94
-
95
- if self.__after_input is None:
96
- self.__after = None
97
- return True
98
- else:
99
- self.__after = self.get_positive_integer_or_raise_exception(self.__after_input, variable_name="after")
100
-
101
- if self.__before > self.__after:
102
- raise ValueError(
103
- f"The 'before' parameter of the truncate function must be less than or equal to the 'after' parameter, "
104
- f"but got: before={self.__before}, after={self.__after}"
105
- )
106
-
107
- return True
108
-
109
- def get_positive_integer_or_raise_exception(
110
- self, variable: PyLegendUnion[date, str, int, None], variable_name: str
111
- ) -> int:
112
- if type(variable) is not int:
113
- raise NotImplementedError(
114
- f"The '{variable_name}' parameter of the truncate function must be an integer, "
115
- f"but got: {variable} (type: {type(variable).__name__})"
116
- )
117
-
118
- if variable < 0:
119
- return 0
120
- return variable
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
+
16
+ from datetime import date
17
+ from pylegend._typing import (
18
+ PyLegendList,
19
+ PyLegendSequence,
20
+ PyLegendUnion,
21
+ PyLegendTuple,
22
+ PyLegendOptional,
23
+ )
24
+ from pylegend.core.sql.metamodel import LongLiteral, QuerySpecification
25
+ from pylegend.core.tds.pandas_api.frames.pandas_api_applied_function_tds_frame import PandasApiAppliedFunction
26
+ from pylegend.core.tds.pandas_api.frames.pandas_api_base_tds_frame import PandasApiBaseTdsFrame
27
+ from pylegend.core.tds.sql_query_helpers import create_sub_query, copy_query
28
+ from pylegend.core.tds.tds_column import TdsColumn
29
+ from pylegend.core.tds.tds_frame import FrameToPureConfig, FrameToSqlConfig
30
+
31
+
32
+ class TruncateFunction(PandasApiAppliedFunction):
33
+ __base_frame: PandasApiBaseTdsFrame
34
+ __before: int
35
+ __after: PyLegendUnion[int, None]
36
+ __axis: PyLegendUnion[str, int]
37
+ __copy: bool
38
+
39
+ @classmethod
40
+ def name(cls) -> str:
41
+ return "truncate" # pragma: no cover
42
+
43
+ def __init__(
44
+ self,
45
+ base_frame: PandasApiBaseTdsFrame,
46
+ before: PyLegendUnion[date, str, int, None],
47
+ after: PyLegendUnion[date, str, int, None],
48
+ axis: PyLegendUnion[str, int],
49
+ copy: bool,
50
+ ) -> None:
51
+ self.__base_frame = base_frame
52
+ self.__before_input = before
53
+ self.__after_input = after
54
+ self.__axis = axis
55
+ self.__copy = copy
56
+
57
+ def to_sql(self, config: FrameToSqlConfig) -> QuerySpecification:
58
+ base_query: QuerySpecification = self.__base_frame.to_sql_query_object(config)
59
+ should_create_sub_query = (base_query.offset is not None) or (base_query.limit is not None)
60
+ new_query = create_sub_query(base_query, config, "root") if should_create_sub_query else copy_query(base_query)
61
+ new_query.offset = LongLiteral(self.__before)
62
+
63
+ if self.__after is not None:
64
+ new_query.limit = LongLiteral(self.__after - self.__before + 1)
65
+ return new_query
66
+
67
+ def to_pure(self, config: FrameToPureConfig) -> str:
68
+ if self.__after is None:
69
+ return f"{self.__base_frame.to_pure(config)}{config.separator(1)}" f"->drop({self.__before})"
70
+
71
+ start_row = self.__before
72
+ end_row = self.__after + 1
73
+ return f"{self.__base_frame.to_pure(config)}{config.separator(1)}" f"->slice({start_row}, {end_row})"
74
+
75
+ def base_frame(self) -> PandasApiBaseTdsFrame:
76
+ return self.__base_frame
77
+
78
+ def tds_frame_parameters(self) -> PyLegendList["PandasApiBaseTdsFrame"]:
79
+ return []
80
+
81
+ def calculate_columns(self) -> PyLegendSequence["TdsColumn"]:
82
+ return [c.copy() for c in self.__base_frame.columns()]
83
+
84
+ def validate(self) -> bool:
85
+ if self.__axis not in [0, "index"]:
86
+ raise NotImplementedError(
87
+ f"The 'axis' parameter of the truncate function must be 0 or 'index', but got: {self.__axis}"
88
+ )
89
+
90
+ if self.__copy not in [True]:
91
+ raise NotImplementedError(f"The 'copy' parameter of the truncate function must be True, but got: {self.__copy}")
92
+
93
+ self.__before, self.__after = self.__normalize_before_and_after(self.__before_input, self.__after_input)
94
+ return True
95
+
96
+ @staticmethod
97
+ def __normalize_before_and_after(
98
+ before_input: PyLegendUnion[date, str, int, None],
99
+ after_input: PyLegendUnion[date, str, int, None]
100
+ ) -> PyLegendTuple[int, PyLegendOptional[int]]:
101
+
102
+ if isinstance(before_input, (date, str)):
103
+ raise NotImplementedError(
104
+ f"The 'before' parameter of the truncate function must be of type integer or None, "
105
+ f"but got: before={before_input} (type: {type(before_input).__name__})")
106
+
107
+ if isinstance(after_input, (date, str)):
108
+ raise NotImplementedError(
109
+ f"The 'after' parameter of the truncate function must be of type integer or None, "
110
+ f"but got: after={after_input} (type: {type(after_input).__name__})")
111
+
112
+ def __raise_error_if_before_gt_after(before_input: int, after_input: int) -> None:
113
+ if before_input > after_input:
114
+ raise ValueError(
115
+ f"The 'before' parameter of the truncate function must be less than or equal to the 'after' parameter, "
116
+ f"but got: before={before_input}, after={after_input}")
117
+
118
+ if before_input is None:
119
+ if after_input is None:
120
+ return 0, None
121
+
122
+ if isinstance(after_input, int) and after_input >= 0:
123
+ return 0, after_input
124
+
125
+ if isinstance(after_input, int) and after_input < 0:
126
+ return 0, -1
127
+
128
+ if isinstance(before_input, int) and before_input >= 0:
129
+ if after_input is None:
130
+ return before_input, None
131
+
132
+ if isinstance(after_input, int) and after_input >= 0:
133
+ __raise_error_if_before_gt_after(before_input, after_input)
134
+ return before_input, after_input
135
+
136
+ if isinstance(after_input, int) and after_input < 0:
137
+ __raise_error_if_before_gt_after(before_input, after_input)
138
+
139
+ if isinstance(before_input, int) and before_input < 0:
140
+ if after_input is None:
141
+ return 0, None
142
+
143
+ if isinstance(after_input, int) and after_input >= 0:
144
+ __raise_error_if_before_gt_after(before_input, after_input)
145
+ return 0, after_input
146
+
147
+ if isinstance(after_input, int) and after_input < 0:
148
+ __raise_error_if_before_gt_after(before_input, after_input)
149
+ return 0, -1
150
+
151
+ return 0, 0 # pragma: no cover
@@ -13,16 +13,17 @@
13
13
  # limitations under the License.
14
14
 
15
15
  from abc import ABCMeta, abstractmethod
16
+
16
17
  from pylegend._typing import (
17
18
  PyLegendSequence,
18
19
  PyLegendList,
20
+ PyLegendType
19
21
  )
20
22
  from pylegend.core.sql.metamodel import QuerySpecification
23
+ from pylegend.core.tds.pandas_api.frames.pandas_api_base_tds_frame import PandasApiBaseTdsFrame
21
24
  from pylegend.core.tds.tds_column import TdsColumn
22
- from pylegend.core.tds.tds_frame import FrameToSqlConfig
23
25
  from pylegend.core.tds.tds_frame import FrameToPureConfig
24
- from pylegend.core.tds.pandas_api.frames.pandas_api_base_tds_frame import PandasApiBaseTdsFrame
25
-
26
+ from pylegend.core.tds.tds_frame import FrameToSqlConfig, PyLegendTdsFrame
26
27
 
27
28
  __all__: PyLegendSequence[str] = [
28
29
  "PandasApiAppliedFunctionTdsFrame",
@@ -69,6 +70,9 @@ class PandasApiAppliedFunctionTdsFrame(PandasApiBaseTdsFrame):
69
70
  super().__init__(columns=applied_function.calculate_columns())
70
71
  self.__applied_function = applied_function
71
72
 
73
+ def get_super_type(self) -> PyLegendType[PyLegendTdsFrame]:
74
+ return type(self) # pragma: no cover
75
+
72
76
  def to_sql_query_object(self, config: FrameToSqlConfig) -> QuerySpecification:
73
77
  return self.__applied_function.to_sql(config)
74
78