pylegend 0.3.0__py3-none-any.whl → 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. pylegend/__init__.py +16 -6
  2. pylegend/core/{databse → database}/sql_to_string/__init__.py +3 -3
  3. pylegend/core/{databse → database}/sql_to_string/db_extension.py +14 -5
  4. pylegend/core/{databse → database}/sql_to_string/generator.py +2 -2
  5. pylegend/core/language/__init__.py +12 -10
  6. pylegend/core/language/legacy_api/__init__.py +13 -0
  7. pylegend/core/language/{aggregate_specification.py → legacy_api/aggregate_specification.py} +10 -10
  8. pylegend/core/language/legacy_api/legacy_api_tds_row.py +32 -0
  9. pylegend/core/language/legendql_api/__init__.py +13 -0
  10. pylegend/core/language/legendql_api/legendql_api_custom_expressions.py +541 -0
  11. pylegend/core/language/legendql_api/legendql_api_tds_row.py +292 -0
  12. pylegend/core/language/shared/__init__.py +13 -0
  13. pylegend/core/language/{column_expressions.py → shared/column_expressions.py} +32 -31
  14. pylegend/core/language/{expression.py → shared/expression.py} +8 -0
  15. pylegend/core/language/{functions.py → shared/functions.py} +12 -3
  16. pylegend/core/language/shared/helpers.py +75 -0
  17. pylegend/core/language/{literal_expressions.py → shared/literal_expressions.py} +39 -1
  18. pylegend/core/language/{operations → shared/operations}/binary_expression.py +34 -2
  19. pylegend/core/language/{operations → shared/operations}/boolean_operation_expressions.py +34 -6
  20. pylegend/core/language/{operations → shared/operations}/collection_operation_expressions.py +146 -26
  21. pylegend/core/language/{operations → shared/operations}/date_operation_expressions.py +288 -24
  22. pylegend/core/language/{operations → shared/operations}/float_operation_expressions.py +53 -8
  23. pylegend/core/language/{operations → shared/operations}/integer_operation_expressions.py +62 -9
  24. pylegend/core/language/{operations → shared/operations}/nullary_expression.py +9 -2
  25. pylegend/core/language/{operations → shared/operations}/number_operation_expressions.py +211 -30
  26. pylegend/core/language/shared/operations/primitive_operation_expressions.py +155 -0
  27. pylegend/core/language/{operations → shared/operations}/string_operation_expressions.py +194 -21
  28. pylegend/core/language/{operations → shared/operations}/unary_expression.py +10 -2
  29. pylegend/core/language/{primitive_collection.py → shared/primitive_collection.py} +2 -2
  30. pylegend/core/language/{primitives → shared/primitives}/__init__.py +9 -9
  31. pylegend/core/language/{primitives → shared/primitives}/boolean.py +9 -5
  32. pylegend/core/language/{primitives → shared/primitives}/date.py +60 -15
  33. pylegend/core/language/{primitives → shared/primitives}/datetime.py +4 -5
  34. pylegend/core/language/{primitives → shared/primitives}/float.py +6 -6
  35. pylegend/core/language/{primitives → shared/primitives}/integer.py +6 -6
  36. pylegend/core/language/{primitives → shared/primitives}/number.py +16 -13
  37. pylegend/core/language/{primitives → shared/primitives}/primitive.py +41 -5
  38. pylegend/core/language/{primitives → shared/primitives}/strictdate.py +4 -5
  39. pylegend/core/language/{primitives → shared/primitives}/string.py +18 -19
  40. pylegend/core/language/{tds_row.py → shared/tds_row.py} +46 -16
  41. pylegend/core/request/__init__.py +7 -1
  42. pylegend/core/request/auth.py +55 -1
  43. pylegend/core/request/legend_client.py +32 -0
  44. pylegend/core/sql/metamodel_extension.py +28 -0
  45. pylegend/core/tds/abstract/__init__.py +13 -0
  46. pylegend/core/tds/abstract/frames/__init__.py +13 -0
  47. pylegend/core/tds/{legend_api/frames/legend_api_applied_function_tds_frame.py → abstract/frames/applied_function_tds_frame.py} +19 -13
  48. pylegend/core/tds/abstract/frames/base_tds_frame.py +125 -0
  49. pylegend/core/tds/{legend_api/frames/legend_api_input_tds_frame.py → abstract/frames/input_tds_frame.py} +9 -12
  50. pylegend/core/tds/{legend_api/frames/functions → abstract}/function_helpers.py +1 -1
  51. pylegend/core/tds/{legend_api/frames/functions/concatenate_function.py → legacy_api/frames/functions/legacy_api_concatenate_function.py} +25 -13
  52. pylegend/core/tds/{legend_api/frames/functions/distinct_function.py → legacy_api/frames/functions/legacy_api_distinct_function.py} +13 -8
  53. pylegend/core/tds/{legend_api/frames/functions/drop_function.py → legacy_api/frames/functions/legacy_api_drop_function.py} +13 -8
  54. pylegend/core/tds/{legend_api/frames/functions/extend_function.py → legacy_api/frames/functions/legacy_api_extend_function.py} +36 -16
  55. pylegend/core/tds/{legend_api/frames/functions/filter_function.py → legacy_api/frames/functions/legacy_api_filter_function.py} +25 -13
  56. pylegend/core/tds/{legend_api/frames/functions/group_by_function.py → legacy_api/frames/functions/legacy_api_group_by_function.py} +44 -17
  57. pylegend/core/tds/{legend_api/frames/functions/head_function.py → legacy_api/frames/functions/legacy_api_head_function.py} +13 -8
  58. pylegend/core/tds/{legend_api/frames/functions/join_by_columns_function.py → legacy_api/frames/functions/legacy_api_join_by_columns_function.py} +40 -13
  59. pylegend/core/tds/{legend_api/frames/functions/join_function.py → legacy_api/frames/functions/legacy_api_join_function.py} +44 -20
  60. pylegend/core/tds/{legend_api/frames/functions/rename_columns_function.py → legacy_api/frames/functions/legacy_api_rename_columns_function.py} +20 -8
  61. pylegend/core/tds/{legend_api/frames/functions/restrict_function.py → legacy_api/frames/functions/legacy_api_restrict_function.py} +17 -8
  62. pylegend/core/tds/{legend_api/frames/functions/slice_function.py → legacy_api/frames/functions/legacy_api_slice_function.py} +13 -8
  63. pylegend/core/tds/{legend_api/frames/functions/sort_function.py → legacy_api/frames/functions/legacy_api_sort_function.py} +19 -8
  64. pylegend/core/tds/legacy_api/frames/legacy_api_applied_function_tds_frame.py +37 -0
  65. pylegend/core/tds/legacy_api/frames/legacy_api_base_tds_frame.py +204 -0
  66. pylegend/core/tds/legacy_api/frames/legacy_api_input_tds_frame.py +51 -0
  67. pylegend/core/tds/{legend_api/frames/legend_api_tds_frame.py → legacy_api/frames/legacy_api_tds_frame.py} +28 -28
  68. pylegend/core/tds/legendql_api/__init__.py +13 -0
  69. pylegend/core/tds/legendql_api/frames/__init__.py +13 -0
  70. pylegend/core/tds/legendql_api/frames/functions/__init__.py +13 -0
  71. pylegend/core/tds/legendql_api/frames/functions/legendql_api_asofjoin_function.py +156 -0
  72. pylegend/core/tds/legendql_api/frames/functions/legendql_api_concatenate_function.py +139 -0
  73. pylegend/core/tds/legendql_api/frames/functions/legendql_api_distinct_function.py +69 -0
  74. pylegend/core/tds/legendql_api/frames/functions/legendql_api_drop_function.py +74 -0
  75. pylegend/core/tds/legendql_api/frames/functions/legendql_api_extend_function.py +256 -0
  76. pylegend/core/tds/legendql_api/frames/functions/legendql_api_filter_function.py +121 -0
  77. pylegend/core/tds/legendql_api/frames/functions/legendql_api_function_helpers.py +137 -0
  78. pylegend/core/tds/legendql_api/frames/functions/legendql_api_groupby_function.py +256 -0
  79. pylegend/core/tds/legendql_api/frames/functions/legendql_api_head_function.py +74 -0
  80. pylegend/core/tds/legendql_api/frames/functions/legendql_api_join_function.py +214 -0
  81. pylegend/core/tds/legendql_api/frames/functions/legendql_api_project_function.py +169 -0
  82. pylegend/core/tds/legendql_api/frames/functions/legendql_api_rename_function.py +189 -0
  83. pylegend/core/tds/legendql_api/frames/functions/legendql_api_select_function.py +131 -0
  84. pylegend/core/tds/legendql_api/frames/functions/legendql_api_slice_function.py +82 -0
  85. pylegend/core/tds/legendql_api/frames/functions/legendql_api_sort_function.py +93 -0
  86. pylegend/core/tds/legendql_api/frames/functions/legendql_api_window_extend_function.py +283 -0
  87. pylegend/core/tds/legendql_api/frames/legendql_api_applied_function_tds_frame.py +37 -0
  88. pylegend/core/tds/legendql_api/frames/legendql_api_base_tds_frame.py +419 -0
  89. pylegend/core/tds/legendql_api/frames/legendql_api_input_tds_frame.py +50 -0
  90. pylegend/core/tds/legendql_api/frames/legendql_api_tds_frame.py +327 -0
  91. pylegend/core/tds/pandas_api/frames/functions/assign_function.py +6 -6
  92. pylegend/core/tds/pandas_api/frames/pandas_api_applied_function_tds_frame.py +4 -0
  93. pylegend/core/tds/pandas_api/frames/pandas_api_base_tds_frame.py +11 -3
  94. pylegend/core/tds/pandas_api/frames/pandas_api_tds_frame.py +2 -2
  95. pylegend/core/tds/tds_frame.py +32 -2
  96. pylegend/extensions/database/vendors/postgres/postgres_sql_to_string.py +1 -1
  97. pylegend/extensions/tds/abstract/legend_function_input_frame.py +4 -0
  98. pylegend/extensions/tds/abstract/legend_service_input_frame.py +4 -0
  99. pylegend/extensions/tds/abstract/table_spec_input_frame.py +4 -0
  100. pylegend/extensions/tds/{legend_api/frames/legend_api_legend_function_input_frame.py → legacy_api/frames/legacy_api_legend_function_input_frame.py} +5 -5
  101. pylegend/extensions/tds/{legend_api/frames/legend_api_legend_service_input_frame.py → legacy_api/frames/legacy_api_legend_service_input_frame.py} +6 -6
  102. pylegend/extensions/tds/{legend_api/frames/legend_api_table_spec_input_frame.py → legacy_api/frames/legacy_api_table_spec_input_frame.py} +5 -5
  103. pylegend/extensions/tds/legendql_api/__init__.py +13 -0
  104. pylegend/extensions/tds/legendql_api/frames/__init__.py +13 -0
  105. pylegend/extensions/tds/legendql_api/frames/legendql_api_legend_service_input_frame.py +46 -0
  106. pylegend/extensions/tds/legendql_api/frames/legendql_api_table_spec_input_frame.py +36 -0
  107. pylegend/{legend_api_tds_client.py → legacy_api_tds_client.py} +15 -15
  108. {pylegend-0.3.0.dist-info → pylegend-0.5.0.dist-info}/METADATA +7 -8
  109. pylegend-0.5.0.dist-info/NOTICE +5 -0
  110. pylegend-0.5.0.dist-info/RECORD +155 -0
  111. {pylegend-0.3.0.dist-info → pylegend-0.5.0.dist-info}/WHEEL +1 -1
  112. pylegend/core/language/operations/primitive_operation_expressions.py +0 -56
  113. pylegend/core/tds/legend_api/frames/legend_api_base_tds_frame.py +0 -294
  114. pylegend-0.3.0.dist-info/RECORD +0 -115
  115. /pylegend/core/{databse → database}/__init__.py +0 -0
  116. /pylegend/core/{databse → database}/sql_to_string/config.py +0 -0
  117. /pylegend/core/language/{operations → shared/operations}/__init__.py +0 -0
  118. /pylegend/core/tds/{legend_api → legacy_api}/__init__.py +0 -0
  119. /pylegend/core/tds/{legend_api → legacy_api}/frames/__init__.py +0 -0
  120. /pylegend/core/tds/{legend_api → legacy_api}/frames/functions/__init__.py +0 -0
  121. /pylegend/extensions/tds/{legend_api → legacy_api}/__init__.py +0 -0
  122. /pylegend/extensions/tds/{legend_api → legacy_api}/frames/__init__.py +0 -0
  123. {pylegend-0.3.0.dist-info → pylegend-0.5.0.dist-info}/LICENSE +0 -0
  124. {pylegend-0.3.0.dist-info → pylegend-0.5.0.dist-info}/LICENSE.spdx +0 -0
@@ -15,6 +15,7 @@
15
15
  from abc import ABCMeta, abstractmethod
16
16
 
17
17
  from requests import PreparedRequest
18
+ from requests.cookies import create_cookie
18
19
 
19
20
  from pylegend._typing import (
20
21
  PyLegendSequence,
@@ -27,7 +28,8 @@ from requests.auth import AuthBase
27
28
  __all__: PyLegendSequence[str] = [
28
29
  "AuthScheme",
29
30
  "LocalhostEmptyAuthScheme",
30
- "HeaderTokenAuthScheme"
31
+ "HeaderTokenAuthScheme",
32
+ "CookieAuthScheme",
31
33
  ]
32
34
 
33
35
 
@@ -83,3 +85,55 @@ class HeaderTokenAuthScheme(AuthScheme):
83
85
 
84
86
  def get_auth_base(self) -> PyLegendOptional[AuthBase]:
85
87
  return HeaderTokenAuth(self.__header_name, self.__token_provider, self.__query_params)
88
+
89
+
90
+ class CookieAuth(AuthBase):
91
+ __cookie_name: str
92
+ __cookie_provider: PyLegendCallable[[], str]
93
+ __query_params: PyLegendOptional[PyLegendDict[str, str]]
94
+
95
+ def __init__( # type: ignore
96
+ self,
97
+ cookie_name: str,
98
+ cookie_provider: PyLegendCallable[[], str],
99
+ query_params: PyLegendOptional[PyLegendDict[str, str]] = None,
100
+ **extra_cookie_params
101
+ ) -> None:
102
+ self.__cookie_name = cookie_name
103
+ self.__cookie_provider = cookie_provider
104
+ self.__query_params = query_params
105
+ self.__extra_cookie_params = extra_cookie_params
106
+
107
+ def __call__(self, r: PreparedRequest) -> PreparedRequest:
108
+ if self.__query_params:
109
+ r.prepare_url(r.url, self.__query_params)
110
+ r.headers.pop("Cookie", None)
111
+ new_cookie = create_cookie( # type: ignore
112
+ name=self.__cookie_name,
113
+ value=self.__cookie_provider(),
114
+ **self.__extra_cookie_params
115
+ )
116
+ r._cookies.set_cookie(new_cookie) # type: ignore
117
+ r.prepare_cookies(r._cookies) # type: ignore
118
+ return r
119
+
120
+
121
+ class CookieAuthScheme(AuthScheme):
122
+ __cookie_name: str
123
+ __cookie_provider: PyLegendCallable[[], str]
124
+ __query_params: PyLegendOptional[PyLegendDict[str, str]]
125
+
126
+ def __init__( # type: ignore
127
+ self,
128
+ cookie_name: str,
129
+ cookie_provider: PyLegendCallable[[], str],
130
+ query_params: PyLegendOptional[PyLegendDict[str, str]] = None,
131
+ **extra_cookie_params
132
+ ) -> None:
133
+ self.__cookie_name = cookie_name
134
+ self.__cookie_provider = cookie_provider
135
+ self.__query_params = query_params
136
+ self.__extra_cookie_params = extra_cookie_params
137
+
138
+ def get_auth_base(self) -> PyLegendOptional[AuthBase]:
139
+ return CookieAuth(self.__cookie_name, self.__cookie_provider, self.__query_params, **self.__extra_cookie_params)
@@ -77,6 +77,38 @@ class LegendClient(ServiceClient):
77
77
  ).iter_content(chunk_size=chunk_size)
78
78
  return ResponseReader(iter_content)
79
79
 
80
+ def parse_model(
81
+ self,
82
+ model_code: str,
83
+ return_source_information: bool = False
84
+ ) -> str:
85
+ response = super()._execute_service(
86
+ method=RequestMethod.POST,
87
+ path="pure/v1/grammar/grammarToJson/model",
88
+ data=model_code,
89
+ headers={"Content-Type": "text/plain"},
90
+ query_params=[("returnSourceInformation", "true" if return_source_information else "false")]
91
+ )
92
+ return response.text
93
+
94
+ def compile_model(
95
+ self,
96
+ model_json: str
97
+ ) -> None:
98
+ super()._execute_service(
99
+ method=RequestMethod.POST,
100
+ path="pure/v1/compilation/compile",
101
+ data=model_json,
102
+ headers={"Content-Type": "application/json"}
103
+ )
104
+
105
+ def parse_and_compile_model(
106
+ self,
107
+ model_code: str
108
+ ) -> None:
109
+ parse_response = self.parse_model(model_code)
110
+ self.compile_model(parse_response)
111
+
80
112
  def __eq__(self, other: 'object') -> bool:
81
113
  if self is other:
82
114
  return True
@@ -19,6 +19,7 @@ from pylegend._typing import (
19
19
  )
20
20
  from pylegend.core.sql.metamodel import (
21
21
  Expression,
22
+ Window,
22
23
  )
23
24
 
24
25
  __all__: PyLegendSequence[str] = [
@@ -78,6 +79,8 @@ __all__: PyLegendSequence[str] = [
78
79
  "MinuteExpression",
79
80
  "SecondExpression",
80
81
  "EpochExpression",
82
+ "WindowExpression",
83
+ "ConstantExpression",
81
84
  ]
82
85
 
83
86
 
@@ -717,3 +720,28 @@ class EpochExpression(Expression):
717
720
  ) -> None:
718
721
  super().__init__(_type="epochExpression")
719
722
  self.value = value
723
+
724
+
725
+ class WindowExpression(Expression):
726
+ nested: "Expression"
727
+ window: "Window"
728
+
729
+ def __init__(
730
+ self,
731
+ nested: "Expression",
732
+ window: "Window",
733
+ ) -> None:
734
+ super().__init__(_type="windowExpression")
735
+ self.nested = nested
736
+ self.window = window
737
+
738
+
739
+ class ConstantExpression(Expression):
740
+ name: str
741
+
742
+ def __init__(
743
+ self,
744
+ name: str
745
+ ) -> None:
746
+ super().__init__(_type="constantExpression")
747
+ self.name = name
@@ -0,0 +1,13 @@
1
+ # Copyright 2025 Goldman Sachs
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
@@ -0,0 +1,13 @@
1
+ # Copyright 2025 Goldman Sachs
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
@@ -15,21 +15,21 @@
15
15
  from abc import ABCMeta, abstractmethod
16
16
  from pylegend._typing import (
17
17
  PyLegendSequence,
18
- PyLegendList,
19
18
  )
20
19
  from pylegend.core.sql.metamodel import QuerySpecification
21
20
  from pylegend.core.tds.tds_column import TdsColumn
22
21
  from pylegend.core.tds.tds_frame import FrameToSqlConfig
23
- from pylegend.core.tds.legend_api.frames.legend_api_base_tds_frame import LegendApiBaseTdsFrame
22
+ from pylegend.core.tds.tds_frame import FrameToPureConfig
23
+ from pylegend.core.tds.abstract.frames.base_tds_frame import BaseTdsFrame
24
24
 
25
25
 
26
26
  __all__: PyLegendSequence[str] = [
27
- "LegendApiAppliedFunctionTdsFrame",
28
- "LegendApiAppliedFunction",
27
+ "AppliedFunctionTdsFrame",
28
+ "AppliedFunction",
29
29
  ]
30
30
 
31
31
 
32
- class LegendApiAppliedFunction(metaclass=ABCMeta):
32
+ class AppliedFunction(metaclass=ABCMeta):
33
33
  @classmethod
34
34
  @abstractmethod
35
35
  def name(cls) -> str:
@@ -39,16 +39,19 @@ class LegendApiAppliedFunction(metaclass=ABCMeta):
39
39
  def to_sql(self, config: FrameToSqlConfig) -> QuerySpecification:
40
40
  pass # pragma: no cover
41
41
 
42
+ def to_pure(self, config: FrameToPureConfig) -> str:
43
+ raise RuntimeError("PURE generation not supported for '" + self.name() + "' function")
44
+
42
45
  @abstractmethod
43
- def base_frame(self) -> LegendApiBaseTdsFrame:
46
+ def base_frame(self) -> BaseTdsFrame:
44
47
  pass # pragma: no cover
45
48
 
46
49
  @abstractmethod
47
- def tds_frame_parameters(self) -> PyLegendList["LegendApiBaseTdsFrame"]:
50
+ def tds_frame_parameters(self) -> PyLegendSequence[BaseTdsFrame]:
48
51
  pass # pragma: no cover
49
52
 
50
53
  @abstractmethod
51
- def calculate_columns(self) -> PyLegendSequence["TdsColumn"]:
54
+ def calculate_columns(self) -> PyLegendSequence[TdsColumn]:
52
55
  pass # pragma: no cover
53
56
 
54
57
  @abstractmethod
@@ -56,10 +59,10 @@ class LegendApiAppliedFunction(metaclass=ABCMeta):
56
59
  pass # pragma: no cover
57
60
 
58
61
 
59
- class LegendApiAppliedFunctionTdsFrame(LegendApiBaseTdsFrame):
60
- __applied_function: LegendApiAppliedFunction
62
+ class AppliedFunctionTdsFrame(BaseTdsFrame, metaclass=ABCMeta):
63
+ __applied_function: AppliedFunction
61
64
 
62
- def __init__(self, applied_function: LegendApiAppliedFunction):
65
+ def __init__(self, applied_function: AppliedFunction):
63
66
  applied_function.validate()
64
67
  super().__init__(columns=applied_function.calculate_columns())
65
68
  self.__applied_function = applied_function
@@ -67,9 +70,12 @@ class LegendApiAppliedFunctionTdsFrame(LegendApiBaseTdsFrame):
67
70
  def to_sql_query_object(self, config: FrameToSqlConfig) -> QuerySpecification:
68
71
  return self.__applied_function.to_sql(config)
69
72
 
70
- def get_all_tds_frames(self) -> PyLegendList["LegendApiBaseTdsFrame"]:
73
+ def to_pure(self, config: FrameToPureConfig) -> str:
74
+ return self.__applied_function.to_pure(config)
75
+
76
+ def get_all_tds_frames(self) -> PyLegendSequence[BaseTdsFrame]:
71
77
  return [
72
78
  y
73
- for x in [self.__applied_function.base_frame()] + self.__applied_function.tds_frame_parameters()
79
+ for x in [self.__applied_function.base_frame()] + list(self.__applied_function.tds_frame_parameters())
74
80
  for y in x.get_all_tds_frames()
75
81
  ] + [self]
@@ -0,0 +1,125 @@
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 abc import ABCMeta, abstractmethod
16
+ import pandas as pd
17
+ from pylegend._typing import (
18
+ PyLegendSequence,
19
+ PyLegendTypeVar,
20
+ PyLegendOptional,
21
+ )
22
+ from pylegend.core.sql.metamodel import QuerySpecification
23
+ from pylegend.core.database.sql_to_string import (
24
+ SqlToStringConfig,
25
+ SqlToStringFormat
26
+ )
27
+ from pylegend.core.tds.tds_column import TdsColumn
28
+ from pylegend.core.tds.tds_frame import FrameToSqlConfig, PyLegendTdsFrame
29
+ from pylegend.core.tds.tds_frame import FrameToPureConfig
30
+ from pylegend.core.tds.result_handler import (
31
+ ResultHandler,
32
+ ToStringResultHandler,
33
+ )
34
+ from pylegend.extensions.tds.result_handler import (
35
+ ToPandasDfResultHandler,
36
+ PandasDfReadConfig,
37
+ )
38
+ __all__: PyLegendSequence[str] = [
39
+ "BaseTdsFrame"
40
+ ]
41
+
42
+ R = PyLegendTypeVar('R')
43
+
44
+
45
+ class BaseTdsFrame(PyLegendTdsFrame, metaclass=ABCMeta):
46
+ __columns: PyLegendSequence[TdsColumn]
47
+
48
+ def __init__(self, columns: PyLegendSequence[TdsColumn]) -> None:
49
+ col_names = [c.get_name() for c in columns]
50
+ if len(col_names) != len(set(col_names)):
51
+ cols = "[" + ", ".join([str(c) for c in columns]) + "]"
52
+ raise ValueError(f"TdsFrame cannot have duplicated column names. Passed columns: {cols}")
53
+ self.__columns = [c.copy() for c in columns]
54
+
55
+ def columns(self) -> PyLegendSequence[TdsColumn]:
56
+ return [c.copy() for c in self.__columns]
57
+
58
+ @abstractmethod
59
+ def to_sql_query_object(self, config: FrameToSqlConfig) -> QuerySpecification:
60
+ pass # pragma: no cover
61
+
62
+ @abstractmethod
63
+ def get_all_tds_frames(self) -> PyLegendSequence["BaseTdsFrame"]:
64
+ pass # pragma: no cover
65
+
66
+ def to_sql_query(self, config: FrameToSqlConfig = FrameToSqlConfig()) -> str:
67
+ query = self.to_sql_query_object(config)
68
+ sql_to_string_config = SqlToStringConfig(format_=SqlToStringFormat(pretty=config.pretty))
69
+ return config.sql_to_string_generator().generate_sql_string(query, sql_to_string_config)
70
+
71
+ @abstractmethod
72
+ def to_pure(self, config: FrameToPureConfig) -> str:
73
+ pass # pragma: no cover
74
+
75
+ def to_pure_query(self, config: FrameToPureConfig = FrameToPureConfig()) -> str:
76
+ return self.to_pure(config)
77
+
78
+ def execute_frame(
79
+ self,
80
+ result_handler: ResultHandler[R],
81
+ chunk_size: PyLegendOptional[int] = None
82
+ ) -> R:
83
+ from pylegend.core.tds.abstract.frames.input_tds_frame import InputTdsFrame, ExecutableInputTdsFrame
84
+
85
+ tds_frames = self.get_all_tds_frames()
86
+ input_frames = [x for x in tds_frames if isinstance(x, InputTdsFrame)]
87
+
88
+ non_exec_frames = [x for x in input_frames if not isinstance(x, ExecutableInputTdsFrame)]
89
+ if non_exec_frames:
90
+ raise ValueError(
91
+ "Cannot execute frame as its built on top of non-executable input frames: [" +
92
+ (", ".join([str(f) for f in non_exec_frames]) + "]")
93
+ )
94
+
95
+ exec_frames = [x for x in input_frames if isinstance(x, ExecutableInputTdsFrame)]
96
+
97
+ all_legend_clients = []
98
+ for e in exec_frames:
99
+ c = e.get_legend_client()
100
+ if c not in all_legend_clients:
101
+ all_legend_clients.append(c)
102
+ if len(all_legend_clients) > 1:
103
+ raise ValueError(
104
+ "Found tds frames with multiple legend_clients (which is not supported): [" +
105
+ (", ".join([str(f) for f in all_legend_clients]) + "]")
106
+ )
107
+ legend_client = all_legend_clients[0]
108
+ result = legend_client.execute_sql_string(self.to_sql_query(), chunk_size=chunk_size)
109
+ return result_handler.handle_result(self, result)
110
+
111
+ def execute_frame_to_string(
112
+ self,
113
+ chunk_size: PyLegendOptional[int] = None
114
+ ) -> str:
115
+ return self.execute_frame(ToStringResultHandler(), chunk_size)
116
+
117
+ def execute_frame_to_pandas_df(
118
+ self,
119
+ chunk_size: PyLegendOptional[int] = None,
120
+ pandas_df_read_config: PandasDfReadConfig = PandasDfReadConfig()
121
+ ) -> pd.DataFrame:
122
+ return self.execute_frame(
123
+ ToPandasDfResultHandler(pandas_df_read_config),
124
+ chunk_size
125
+ )
@@ -1,4 +1,4 @@
1
- # Copyright 2023 Goldman Sachs
1
+ # Copyright 2025 Goldman Sachs
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -15,30 +15,28 @@
15
15
  from abc import ABCMeta
16
16
  from pylegend._typing import (
17
17
  PyLegendSequence,
18
- PyLegendList
19
18
  )
20
19
  from pylegend.core.tds.tds_column import TdsColumn
21
- from pylegend.core.tds.legend_api.frames.legend_api_base_tds_frame import LegendApiBaseTdsFrame
20
+ from pylegend.core.tds.abstract.frames.base_tds_frame import BaseTdsFrame
22
21
  from pylegend.core.request.legend_client import LegendClient
23
22
 
24
23
 
25
24
  __all__: PyLegendSequence[str] = [
26
- "LegendApiExecutableInputTdsFrame",
27
- "LegendApiNonExecutableInputTdsFrame",
28
- "LegendApiInputTdsFrame"
25
+ "ExecutableInputTdsFrame",
26
+ "NonExecutableInputTdsFrame",
27
+ "InputTdsFrame"
29
28
  ]
30
29
 
31
30
 
32
- class LegendApiInputTdsFrame(LegendApiBaseTdsFrame, metaclass=ABCMeta):
33
-
31
+ class InputTdsFrame(BaseTdsFrame, metaclass=ABCMeta):
34
32
  def __init__(self, columns: PyLegendSequence[TdsColumn]) -> None:
35
33
  super().__init__(columns=columns)
36
34
 
37
- def get_all_tds_frames(self) -> PyLegendList["LegendApiBaseTdsFrame"]:
35
+ def get_all_tds_frames(self) -> PyLegendSequence["BaseTdsFrame"]:
38
36
  return [self]
39
37
 
40
38
 
41
- class LegendApiExecutableInputTdsFrame(LegendApiInputTdsFrame, metaclass=ABCMeta):
39
+ class ExecutableInputTdsFrame(InputTdsFrame, metaclass=ABCMeta):
42
40
  __legend_client: LegendClient
43
41
 
44
42
  def __init__(self, legend_client: LegendClient, columns: PyLegendSequence[TdsColumn]) -> None:
@@ -49,7 +47,6 @@ class LegendApiExecutableInputTdsFrame(LegendApiInputTdsFrame, metaclass=ABCMeta
49
47
  return self.__legend_client
50
48
 
51
49
 
52
- class LegendApiNonExecutableInputTdsFrame(LegendApiInputTdsFrame, metaclass=ABCMeta):
53
-
50
+ class NonExecutableInputTdsFrame(InputTdsFrame, metaclass=ABCMeta):
54
51
  def __init__(self, columns: PyLegendSequence[TdsColumn]) -> None:
55
52
  super().__init__(columns=columns)
@@ -1,4 +1,4 @@
1
- # Copyright 2023 Goldman Sachs
1
+ # Copyright 2025 Goldman Sachs
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -16,7 +16,7 @@ from pylegend._typing import (
16
16
  PyLegendList,
17
17
  PyLegendSequence
18
18
  )
19
- from pylegend.core.tds.legend_api.frames.legend_api_applied_function_tds_frame import LegendApiAppliedFunction
19
+ from pylegend.core.tds.legacy_api.frames.legacy_api_applied_function_tds_frame import LegacyApiAppliedFunction
20
20
  from pylegend.core.sql.metamodel import (
21
21
  QuerySpecification,
22
22
  Union,
@@ -28,29 +28,31 @@ from pylegend.core.sql.metamodel import (
28
28
  TableSubquery,
29
29
  Query,
30
30
  )
31
+ from pylegend.core.tds.sql_query_helpers import create_sub_query
31
32
  from pylegend.core.tds.tds_column import TdsColumn
32
33
  from pylegend.core.tds.tds_frame import FrameToSqlConfig
33
- from pylegend.core.tds.legend_api.frames.legend_api_tds_frame import LegendApiTdsFrame
34
- from pylegend.core.tds.legend_api.frames.legend_api_base_tds_frame import LegendApiBaseTdsFrame
34
+ from pylegend.core.tds.tds_frame import FrameToPureConfig
35
+ from pylegend.core.tds.legacy_api.frames.legacy_api_tds_frame import LegacyApiTdsFrame
36
+ from pylegend.core.tds.legacy_api.frames.legacy_api_base_tds_frame import LegacyApiBaseTdsFrame
35
37
 
36
38
 
37
39
  __all__: PyLegendSequence[str] = [
38
- "ConcatenateFunction"
40
+ "LegacyApiConcatenateFunction"
39
41
  ]
40
42
 
41
43
 
42
- class ConcatenateFunction(LegendApiAppliedFunction):
43
- __base_frame: LegendApiBaseTdsFrame
44
- __other_frame: LegendApiBaseTdsFrame
44
+ class LegacyApiConcatenateFunction(LegacyApiAppliedFunction):
45
+ __base_frame: LegacyApiBaseTdsFrame
46
+ __other_frame: LegacyApiBaseTdsFrame
45
47
 
46
48
  @classmethod
47
49
  def name(cls) -> str:
48
50
  return "concatenate"
49
51
 
50
- def __init__(self, base_frame: LegendApiBaseTdsFrame, other: LegendApiTdsFrame) -> None:
52
+ def __init__(self, base_frame: LegacyApiBaseTdsFrame, other: LegacyApiTdsFrame) -> None:
51
53
  self.__base_frame = base_frame
52
- if not isinstance(other, LegendApiBaseTdsFrame):
53
- raise ValueError("Expected LegendApiBaseTdsFrame") # pragma: no cover
54
+ if not isinstance(other, LegacyApiBaseTdsFrame):
55
+ raise ValueError("Expected LegacyApiBaseTdsFrame") # pragma: no cover
54
56
  self.__other_frame = other
55
57
 
56
58
  def to_sql(self, config: FrameToSqlConfig) -> QuerySpecification:
@@ -76,7 +78,11 @@ class ConcatenateFunction(LegendApiAppliedFunction):
76
78
  AliasedRelation(
77
79
  relation=TableSubquery(
78
80
  query=Query(
79
- queryBody=Union(left=base_query, right=other_query, distinct=False),
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
+ ),
80
86
  limit=None, offset=None, orderBy=[]
81
87
  )
82
88
  ),
@@ -92,10 +98,16 @@ class ConcatenateFunction(LegendApiAppliedFunction):
92
98
  offset=None
93
99
  )
94
100
 
95
- def base_frame(self) -> LegendApiBaseTdsFrame:
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) -> LegacyApiBaseTdsFrame:
96
108
  return self.__base_frame
97
109
 
98
- def tds_frame_parameters(self) -> PyLegendList["LegendApiBaseTdsFrame"]:
110
+ def tds_frame_parameters(self) -> PyLegendList["LegacyApiBaseTdsFrame"]:
99
111
  return [self.__other_frame]
100
112
 
101
113
  def calculate_columns(self) -> PyLegendSequence["TdsColumn"]:
@@ -16,29 +16,30 @@ from pylegend._typing import (
16
16
  PyLegendList,
17
17
  PyLegendSequence
18
18
  )
19
- from pylegend.core.tds.legend_api.frames.legend_api_applied_function_tds_frame import LegendApiAppliedFunction
19
+ from pylegend.core.tds.legacy_api.frames.legacy_api_applied_function_tds_frame import LegacyApiAppliedFunction
20
20
  from pylegend.core.tds.sql_query_helpers import copy_query, create_sub_query
21
21
  from pylegend.core.sql.metamodel import (
22
22
  QuerySpecification,
23
23
  )
24
24
  from pylegend.core.tds.tds_column import TdsColumn
25
25
  from pylegend.core.tds.tds_frame import FrameToSqlConfig
26
- from pylegend.core.tds.legend_api.frames.legend_api_base_tds_frame import LegendApiBaseTdsFrame
26
+ from pylegend.core.tds.tds_frame import FrameToPureConfig
27
+ from pylegend.core.tds.legacy_api.frames.legacy_api_base_tds_frame import LegacyApiBaseTdsFrame
27
28
 
28
29
 
29
30
  __all__: PyLegendSequence[str] = [
30
- "DistinctFunction"
31
+ "LegacyApiDistinctFunction"
31
32
  ]
32
33
 
33
34
 
34
- class DistinctFunction(LegendApiAppliedFunction):
35
- __base_frame: LegendApiBaseTdsFrame
35
+ class LegacyApiDistinctFunction(LegacyApiAppliedFunction):
36
+ __base_frame: LegacyApiBaseTdsFrame
36
37
 
37
38
  @classmethod
38
39
  def name(cls) -> str:
39
40
  return "distinct"
40
41
 
41
- def __init__(self, base_frame: LegendApiBaseTdsFrame) -> None:
42
+ def __init__(self, base_frame: LegacyApiBaseTdsFrame) -> None:
42
43
  self.__base_frame = base_frame
43
44
 
44
45
  def to_sql(self, config: FrameToSqlConfig) -> QuerySpecification:
@@ -51,10 +52,14 @@ class DistinctFunction(LegendApiAppliedFunction):
51
52
  new_query.select.distinct = True
52
53
  return new_query
53
54
 
54
- def base_frame(self) -> LegendApiBaseTdsFrame:
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) -> LegacyApiBaseTdsFrame:
55
60
  return self.__base_frame
56
61
 
57
- def tds_frame_parameters(self) -> PyLegendList["LegendApiBaseTdsFrame"]:
62
+ def tds_frame_parameters(self) -> PyLegendList["LegacyApiBaseTdsFrame"]:
58
63
  return []
59
64
 
60
65
  def calculate_columns(self) -> PyLegendSequence["TdsColumn"]:
@@ -16,7 +16,7 @@ from pylegend._typing import (
16
16
  PyLegendList,
17
17
  PyLegendSequence
18
18
  )
19
- from pylegend.core.tds.legend_api.frames.legend_api_applied_function_tds_frame import LegendApiAppliedFunction
19
+ from pylegend.core.tds.legacy_api.frames.legacy_api_applied_function_tds_frame import LegacyApiAppliedFunction
20
20
  from pylegend.core.tds.sql_query_helpers import copy_query, create_sub_query
21
21
  from pylegend.core.sql.metamodel import (
22
22
  QuerySpecification,
@@ -24,23 +24,24 @@ from pylegend.core.sql.metamodel import (
24
24
  )
25
25
  from pylegend.core.tds.tds_column import TdsColumn
26
26
  from pylegend.core.tds.tds_frame import FrameToSqlConfig
27
- from pylegend.core.tds.legend_api.frames.legend_api_base_tds_frame import LegendApiBaseTdsFrame
27
+ from pylegend.core.tds.tds_frame import FrameToPureConfig
28
+ from pylegend.core.tds.legacy_api.frames.legacy_api_base_tds_frame import LegacyApiBaseTdsFrame
28
29
 
29
30
 
30
31
  __all__: PyLegendSequence[str] = [
31
- "DropFunction"
32
+ "LegacyApiDropFunction"
32
33
  ]
33
34
 
34
35
 
35
- class DropFunction(LegendApiAppliedFunction):
36
- __base_frame: LegendApiBaseTdsFrame
36
+ class LegacyApiDropFunction(LegacyApiAppliedFunction):
37
+ __base_frame: LegacyApiBaseTdsFrame
37
38
  __row_count: int
38
39
 
39
40
  @classmethod
40
41
  def name(cls) -> str:
41
42
  return "drop"
42
43
 
43
- def __init__(self, base_frame: LegendApiBaseTdsFrame, row_count: int) -> None:
44
+ def __init__(self, base_frame: LegacyApiBaseTdsFrame, row_count: int) -> None:
44
45
  self.__base_frame = base_frame
45
46
  self.__row_count = row_count
46
47
 
@@ -54,10 +55,14 @@ class DropFunction(LegendApiAppliedFunction):
54
55
  new_query.offset = LongLiteral(value=self.__row_count)
55
56
  return new_query
56
57
 
57
- def base_frame(self) -> LegendApiBaseTdsFrame:
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) -> LegacyApiBaseTdsFrame:
58
63
  return self.__base_frame
59
64
 
60
- def tds_frame_parameters(self) -> PyLegendList["LegendApiBaseTdsFrame"]:
65
+ def tds_frame_parameters(self) -> PyLegendList["LegacyApiBaseTdsFrame"]:
61
66
  return []
62
67
 
63
68
  def calculate_columns(self) -> PyLegendSequence["TdsColumn"]: