py-flink-sql-gateway 0.1.0.dev0__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.
- flink_gateway/__init__.py +104 -0
- flink_gateway/_version.py +34 -0
- flink_gateway/client.py +302 -0
- flink_gateway/connection.py +109 -0
- flink_gateway/cursor.py +311 -0
- flink_gateway/exceptions.py +43 -0
- flink_gateway/models.py +235 -0
- flink_gateway/py.typed +0 -0
- flink_gateway/types.py +396 -0
- py_flink_sql_gateway-0.1.0.dev0.dist-info/METADATA +128 -0
- py_flink_sql_gateway-0.1.0.dev0.dist-info/RECORD +12 -0
- py_flink_sql_gateway-0.1.0.dev0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Flink SQL Gateway — Python client library.
|
|
2
|
+
|
|
3
|
+
Implements PEP 249 (DB-API 2.0) on top of the Flink SQL Gateway REST API.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from flink_gateway.client import FlinkSqlGatewayClient
|
|
7
|
+
from flink_gateway.connection import Connection, connect
|
|
8
|
+
from flink_gateway.cursor import Cursor
|
|
9
|
+
from flink_gateway.exceptions import (
|
|
10
|
+
DatabaseError,
|
|
11
|
+
Error,
|
|
12
|
+
FlinkSqlGatewayError,
|
|
13
|
+
InterfaceError,
|
|
14
|
+
NotSupportedError,
|
|
15
|
+
OperationalError,
|
|
16
|
+
ProgrammingError,
|
|
17
|
+
)
|
|
18
|
+
from flink_gateway.models import (
|
|
19
|
+
ColumnInfo,
|
|
20
|
+
CompleteStatementRequest,
|
|
21
|
+
ConfigureSessionRequest,
|
|
22
|
+
ExecuteStatementRequest,
|
|
23
|
+
FetchResultsResponse,
|
|
24
|
+
InfoResponse,
|
|
25
|
+
LogicalType,
|
|
26
|
+
OpenSessionRequest,
|
|
27
|
+
RefreshMaterializedTableRequest,
|
|
28
|
+
ResultKind,
|
|
29
|
+
ResultSet,
|
|
30
|
+
ResultType,
|
|
31
|
+
RowData,
|
|
32
|
+
)
|
|
33
|
+
from flink_gateway.types import (
|
|
34
|
+
BINARY,
|
|
35
|
+
DATETIME,
|
|
36
|
+
NUMBER,
|
|
37
|
+
ROWID,
|
|
38
|
+
STRING,
|
|
39
|
+
Binary,
|
|
40
|
+
Date,
|
|
41
|
+
DateFromTicks,
|
|
42
|
+
DBAPITypeObject,
|
|
43
|
+
FlinkType,
|
|
44
|
+
Time,
|
|
45
|
+
TimeFromTicks,
|
|
46
|
+
Timestamp,
|
|
47
|
+
TimestampFromTicks,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# PEP 249 module-level globals
|
|
51
|
+
apilevel = "2.0"
|
|
52
|
+
threadsafety = 1 # Threads may share the module but not connections.
|
|
53
|
+
paramstyle = "qmark"
|
|
54
|
+
|
|
55
|
+
__all__ = [
|
|
56
|
+
# PEP 249
|
|
57
|
+
"apilevel",
|
|
58
|
+
"threadsafety",
|
|
59
|
+
"paramstyle",
|
|
60
|
+
"connect",
|
|
61
|
+
"Connection",
|
|
62
|
+
"Cursor",
|
|
63
|
+
# Exceptions
|
|
64
|
+
"Error",
|
|
65
|
+
"InterfaceError",
|
|
66
|
+
"DatabaseError",
|
|
67
|
+
"OperationalError",
|
|
68
|
+
"FlinkSqlGatewayError",
|
|
69
|
+
"ProgrammingError",
|
|
70
|
+
"NotSupportedError",
|
|
71
|
+
# Type objects
|
|
72
|
+
"STRING",
|
|
73
|
+
"BINARY",
|
|
74
|
+
"NUMBER",
|
|
75
|
+
"DATETIME",
|
|
76
|
+
"ROWID",
|
|
77
|
+
"DBAPITypeObject",
|
|
78
|
+
"FlinkType",
|
|
79
|
+
# PEP 249 constructors
|
|
80
|
+
"Date",
|
|
81
|
+
"Time",
|
|
82
|
+
"Timestamp",
|
|
83
|
+
"DateFromTicks",
|
|
84
|
+
"TimeFromTicks",
|
|
85
|
+
"TimestampFromTicks",
|
|
86
|
+
"Binary",
|
|
87
|
+
# Low-level client
|
|
88
|
+
"FlinkSqlGatewayClient",
|
|
89
|
+
"FlinkSqlGatewayError",
|
|
90
|
+
# Models
|
|
91
|
+
"ColumnInfo",
|
|
92
|
+
"CompleteStatementRequest",
|
|
93
|
+
"ConfigureSessionRequest",
|
|
94
|
+
"ExecuteStatementRequest",
|
|
95
|
+
"FetchResultsResponse",
|
|
96
|
+
"InfoResponse",
|
|
97
|
+
"LogicalType",
|
|
98
|
+
"OpenSessionRequest",
|
|
99
|
+
"RefreshMaterializedTableRequest",
|
|
100
|
+
"ResultKind",
|
|
101
|
+
"ResultSet",
|
|
102
|
+
"ResultType",
|
|
103
|
+
"RowData",
|
|
104
|
+
]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"__version__",
|
|
6
|
+
"__version_tuple__",
|
|
7
|
+
"version",
|
|
8
|
+
"version_tuple",
|
|
9
|
+
"__commit_id__",
|
|
10
|
+
"commit_id",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
TYPE_CHECKING = False
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from typing import Tuple
|
|
16
|
+
from typing import Union
|
|
17
|
+
|
|
18
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
19
|
+
COMMIT_ID = Union[str, None]
|
|
20
|
+
else:
|
|
21
|
+
VERSION_TUPLE = object
|
|
22
|
+
COMMIT_ID = object
|
|
23
|
+
|
|
24
|
+
version: str
|
|
25
|
+
__version__: str
|
|
26
|
+
__version_tuple__: VERSION_TUPLE
|
|
27
|
+
version_tuple: VERSION_TUPLE
|
|
28
|
+
commit_id: COMMIT_ID
|
|
29
|
+
__commit_id__: COMMIT_ID
|
|
30
|
+
|
|
31
|
+
__version__ = version = '0.1.0.dev0'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 1, 0, 'dev0')
|
|
33
|
+
|
|
34
|
+
__commit_id__ = commit_id = None
|
flink_gateway/client.py
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
"""Flink SQL Gateway REST API client.
|
|
2
|
+
|
|
3
|
+
This module provides a low-level HTTP client that wraps every endpoint of
|
|
4
|
+
the Apache Flink SQL Gateway REST API.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
import httpx
|
|
12
|
+
|
|
13
|
+
from flink_gateway.exceptions import FlinkSqlGatewayError
|
|
14
|
+
from flink_gateway.models import (
|
|
15
|
+
CompleteStatementRequest,
|
|
16
|
+
ConfigureSessionRequest,
|
|
17
|
+
ExecuteStatementRequest,
|
|
18
|
+
FetchResultsResponse,
|
|
19
|
+
InfoResponse,
|
|
20
|
+
OpenSessionRequest,
|
|
21
|
+
RefreshMaterializedTableRequest,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class FlinkSqlGatewayClient:
|
|
26
|
+
"""Low-level client for the Flink SQL Gateway REST API.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
base_url: Root URL of the SQL Gateway, e.g. ``http://localhost:8083``.
|
|
30
|
+
http_client: Optional pre-configured ``httpx.Client``. When *None*, a
|
|
31
|
+
default client is created with a 30-second timeout.
|
|
32
|
+
api_version: REST API version prefix (``"v1"``, ``"v2"``, or ``"v3"``).
|
|
33
|
+
Defaults to ``"v3"``.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
base_url: str,
|
|
39
|
+
http_client: httpx.Client | None = None,
|
|
40
|
+
api_version: str = "v3",
|
|
41
|
+
) -> None:
|
|
42
|
+
self._base_url = base_url.rstrip("/")
|
|
43
|
+
self._api_version = api_version or "v3"
|
|
44
|
+
self._owns_client = http_client is None
|
|
45
|
+
self._client = http_client or httpx.Client(timeout=30.0)
|
|
46
|
+
|
|
47
|
+
# ── Context manager ────────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
def __enter__(self) -> FlinkSqlGatewayClient:
|
|
50
|
+
return self
|
|
51
|
+
|
|
52
|
+
def __exit__(self, *_: Any) -> None:
|
|
53
|
+
self.close()
|
|
54
|
+
|
|
55
|
+
def close(self) -> None:
|
|
56
|
+
"""Close the underlying HTTP client if we own it."""
|
|
57
|
+
if self._owns_client:
|
|
58
|
+
self._client.close()
|
|
59
|
+
|
|
60
|
+
# ── URL helpers ────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
def _build_endpoint(self, *segments: str) -> str:
|
|
63
|
+
parts = [self._base_url, self._api_version, *segments]
|
|
64
|
+
return "/".join(parts)
|
|
65
|
+
|
|
66
|
+
# ── Metadata ───────────────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
def get_info(self) -> InfoResponse:
|
|
69
|
+
"""GET /info — cluster metadata."""
|
|
70
|
+
url = self._build_endpoint("info")
|
|
71
|
+
resp = self._client.get(url)
|
|
72
|
+
self._check_response(resp, "get info")
|
|
73
|
+
data = resp.json()
|
|
74
|
+
return InfoResponse(
|
|
75
|
+
product_name=data.get("productName", ""),
|
|
76
|
+
version=data.get("version", ""),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
def get_api_versions(self) -> list[str]:
|
|
80
|
+
"""GET /api_versions — supported REST API versions."""
|
|
81
|
+
url = self._build_endpoint("api_versions")
|
|
82
|
+
resp = self._client.get(url)
|
|
83
|
+
self._check_response(resp, "get api versions")
|
|
84
|
+
return resp.json().get("versions", [])
|
|
85
|
+
|
|
86
|
+
# ── Session management ─────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
def open_session(
|
|
89
|
+
self,
|
|
90
|
+
request: OpenSessionRequest | None = None,
|
|
91
|
+
) -> str:
|
|
92
|
+
"""POST /sessions — open a new session.
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
The session handle string.
|
|
96
|
+
"""
|
|
97
|
+
url = self._build_endpoint("sessions")
|
|
98
|
+
body = request.to_dict() if request else {}
|
|
99
|
+
resp = self._client.post(url, json=body)
|
|
100
|
+
self._check_response(resp, "open session")
|
|
101
|
+
return resp.json()["sessionHandle"]
|
|
102
|
+
|
|
103
|
+
def close_session(self, session_handle: str) -> None:
|
|
104
|
+
"""DELETE /sessions/{session_handle}."""
|
|
105
|
+
url = self._build_endpoint("sessions", session_handle)
|
|
106
|
+
resp = self._client.delete(url)
|
|
107
|
+
self._check_response(resp, "close session")
|
|
108
|
+
|
|
109
|
+
def get_session_config(self, session_handle: str) -> dict[str, str]:
|
|
110
|
+
"""GET /sessions/{session_handle} — current session properties."""
|
|
111
|
+
url = self._build_endpoint("sessions", session_handle)
|
|
112
|
+
resp = self._client.get(url)
|
|
113
|
+
self._check_response(resp, "get session config")
|
|
114
|
+
return resp.json().get("properties", {})
|
|
115
|
+
|
|
116
|
+
def configure_session(
|
|
117
|
+
self,
|
|
118
|
+
session_handle: str,
|
|
119
|
+
request: ConfigureSessionRequest,
|
|
120
|
+
) -> None:
|
|
121
|
+
"""POST /sessions/{session_handle}/configure-session."""
|
|
122
|
+
url = self._build_endpoint("sessions", session_handle, "configure-session")
|
|
123
|
+
resp = self._client.post(url, json=request.to_dict())
|
|
124
|
+
self._check_response(resp, "configure session")
|
|
125
|
+
|
|
126
|
+
def heartbeat(self, session_handle: str) -> None:
|
|
127
|
+
"""POST /sessions/{session_handle}/heartbeat."""
|
|
128
|
+
url = self._build_endpoint("sessions", session_handle, "heartbeat")
|
|
129
|
+
resp = self._client.post(url, json={})
|
|
130
|
+
self._check_response(resp, "heartbeat")
|
|
131
|
+
|
|
132
|
+
def complete_statement(
|
|
133
|
+
self,
|
|
134
|
+
session_handle: str,
|
|
135
|
+
request: CompleteStatementRequest,
|
|
136
|
+
) -> list[str]:
|
|
137
|
+
"""GET /sessions/{session_handle}/complete-statement.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
List of completion candidates.
|
|
141
|
+
"""
|
|
142
|
+
url = self._build_endpoint("sessions", session_handle, "complete-statement")
|
|
143
|
+
# The Flink Gateway uses GET with a JSON body for this endpoint.
|
|
144
|
+
resp = self._client.request("GET", url, json=request.to_dict())
|
|
145
|
+
self._check_response(resp, "complete statement")
|
|
146
|
+
return resp.json().get("candidates", [])
|
|
147
|
+
|
|
148
|
+
# ── Statement execution ────────────────────────────────────────────
|
|
149
|
+
|
|
150
|
+
def execute_statement(
|
|
151
|
+
self,
|
|
152
|
+
session_handle: str,
|
|
153
|
+
request: ExecuteStatementRequest,
|
|
154
|
+
) -> str:
|
|
155
|
+
"""POST /sessions/{session_handle}/statements.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
The operation handle string.
|
|
159
|
+
"""
|
|
160
|
+
url = self._build_endpoint("sessions", session_handle, "statements")
|
|
161
|
+
resp = self._client.post(url, json=request.to_dict())
|
|
162
|
+
self._check_response(resp, "execute statement")
|
|
163
|
+
return resp.json()["operationHandle"]
|
|
164
|
+
|
|
165
|
+
def fetch_results(
|
|
166
|
+
self,
|
|
167
|
+
session_handle: str,
|
|
168
|
+
operation_handle: str,
|
|
169
|
+
token: str,
|
|
170
|
+
row_format: str = "",
|
|
171
|
+
) -> FetchResultsResponse:
|
|
172
|
+
"""GET /sessions/{sh}/operations/{oh}/result/{token}.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
session_handle: Session handle.
|
|
176
|
+
operation_handle: Operation handle.
|
|
177
|
+
token: Pagination token (``"0"`` for the first batch).
|
|
178
|
+
row_format: Optional row format (e.g. ``"json"``).
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
Parsed :class:`FetchResultsResponse`.
|
|
182
|
+
"""
|
|
183
|
+
url = self._build_endpoint(
|
|
184
|
+
"sessions",
|
|
185
|
+
session_handle,
|
|
186
|
+
"operations",
|
|
187
|
+
operation_handle,
|
|
188
|
+
"result",
|
|
189
|
+
token,
|
|
190
|
+
)
|
|
191
|
+
params: dict[str, str] = {}
|
|
192
|
+
if row_format:
|
|
193
|
+
params["rowFormat"] = row_format
|
|
194
|
+
resp = self._client.get(url, params=params)
|
|
195
|
+
self._check_response(resp, "fetch results")
|
|
196
|
+
return FetchResultsResponse.from_dict(resp.json())
|
|
197
|
+
|
|
198
|
+
# ── Operation management ───────────────────────────────────────────
|
|
199
|
+
|
|
200
|
+
def get_operation_status(
|
|
201
|
+
self,
|
|
202
|
+
session_handle: str,
|
|
203
|
+
operation_handle: str,
|
|
204
|
+
) -> str:
|
|
205
|
+
"""GET /sessions/{sh}/operations/{oh}/status.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
The status string (e.g. ``"RUNNING"``, ``"FINISHED"``).
|
|
209
|
+
"""
|
|
210
|
+
url = self._build_endpoint(
|
|
211
|
+
"sessions",
|
|
212
|
+
session_handle,
|
|
213
|
+
"operations",
|
|
214
|
+
operation_handle,
|
|
215
|
+
"status",
|
|
216
|
+
)
|
|
217
|
+
resp = self._client.get(url)
|
|
218
|
+
self._check_response(resp, "get operation status")
|
|
219
|
+
return resp.json()["status"]
|
|
220
|
+
|
|
221
|
+
def cancel_operation(
|
|
222
|
+
self,
|
|
223
|
+
session_handle: str,
|
|
224
|
+
operation_handle: str,
|
|
225
|
+
) -> str:
|
|
226
|
+
"""POST /sessions/{sh}/operations/{oh}/cancel.
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
The operation status after cancellation.
|
|
230
|
+
"""
|
|
231
|
+
url = self._build_endpoint(
|
|
232
|
+
"sessions",
|
|
233
|
+
session_handle,
|
|
234
|
+
"operations",
|
|
235
|
+
operation_handle,
|
|
236
|
+
"cancel",
|
|
237
|
+
)
|
|
238
|
+
resp = self._client.post(url, json={})
|
|
239
|
+
self._check_response(resp, "cancel operation")
|
|
240
|
+
return resp.json()["status"]
|
|
241
|
+
|
|
242
|
+
def close_operation(
|
|
243
|
+
self,
|
|
244
|
+
session_handle: str,
|
|
245
|
+
operation_handle: str,
|
|
246
|
+
) -> str:
|
|
247
|
+
"""DELETE /sessions/{sh}/operations/{oh}.
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
The operation status after closure.
|
|
251
|
+
"""
|
|
252
|
+
url = self._build_endpoint(
|
|
253
|
+
"sessions",
|
|
254
|
+
session_handle,
|
|
255
|
+
"operations",
|
|
256
|
+
operation_handle,
|
|
257
|
+
)
|
|
258
|
+
resp = self._client.delete(url)
|
|
259
|
+
self._check_response(resp, "close operation")
|
|
260
|
+
return resp.json()["status"]
|
|
261
|
+
|
|
262
|
+
# ── Materialized tables ────────────────────────────────────────────
|
|
263
|
+
|
|
264
|
+
def refresh_materialized_table(
|
|
265
|
+
self,
|
|
266
|
+
session_handle: str,
|
|
267
|
+
identifier: str,
|
|
268
|
+
request: RefreshMaterializedTableRequest | None = None,
|
|
269
|
+
) -> str:
|
|
270
|
+
"""POST /sessions/{sh}/materialized-tables/{id}/refresh.
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
The operation handle string.
|
|
274
|
+
"""
|
|
275
|
+
url = self._build_endpoint(
|
|
276
|
+
"sessions",
|
|
277
|
+
session_handle,
|
|
278
|
+
"materialized-tables",
|
|
279
|
+
identifier,
|
|
280
|
+
"refresh",
|
|
281
|
+
)
|
|
282
|
+
body = request.to_dict() if request else {}
|
|
283
|
+
resp = self._client.post(url, json=body)
|
|
284
|
+
self._check_response(resp, "refresh materialized table")
|
|
285
|
+
return resp.json()["operationHandle"]
|
|
286
|
+
|
|
287
|
+
# ── Internal helpers ───────────────────────────────────────────────
|
|
288
|
+
|
|
289
|
+
@staticmethod
|
|
290
|
+
def _check_response(resp: httpx.Response, action: str) -> None:
|
|
291
|
+
if resp.status_code == 200:
|
|
292
|
+
return
|
|
293
|
+
detail = ""
|
|
294
|
+
try:
|
|
295
|
+
errors = resp.json().get("errors", [])
|
|
296
|
+
if errors:
|
|
297
|
+
detail = ": " + "; ".join(errors)
|
|
298
|
+
except (ValueError, KeyError):
|
|
299
|
+
pass
|
|
300
|
+
raise FlinkSqlGatewayError(
|
|
301
|
+
f"{action} failed: {resp.status_code} {resp.reason_phrase}{detail}"
|
|
302
|
+
)
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""PEP 249 Connection implementation for the Flink SQL Gateway."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from flink_gateway.client import FlinkSqlGatewayClient
|
|
8
|
+
from flink_gateway.cursor import Cursor
|
|
9
|
+
from flink_gateway.exceptions import NotSupportedError, ProgrammingError
|
|
10
|
+
from flink_gateway.models import OpenSessionRequest
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Connection:
|
|
14
|
+
"""PEP 249 Connection to a Flink SQL Gateway.
|
|
15
|
+
|
|
16
|
+
Do not instantiate directly; use :func:`flink_gateway.connect`.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
client: FlinkSqlGatewayClient,
|
|
22
|
+
session_handle: str,
|
|
23
|
+
*,
|
|
24
|
+
_owns_client: bool = True,
|
|
25
|
+
) -> None:
|
|
26
|
+
self._client = client
|
|
27
|
+
self._session_handle = session_handle
|
|
28
|
+
self._owns_client = _owns_client
|
|
29
|
+
self._closed = False
|
|
30
|
+
|
|
31
|
+
# ── Public read-only properties ────────────────────────────────
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def client(self) -> FlinkSqlGatewayClient:
|
|
35
|
+
"""The underlying REST client."""
|
|
36
|
+
return self._client
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def session_handle(self) -> str:
|
|
40
|
+
"""The session handle for this connection."""
|
|
41
|
+
return self._session_handle
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def closed(self) -> bool:
|
|
45
|
+
"""Whether this connection has been closed."""
|
|
46
|
+
return self._closed
|
|
47
|
+
|
|
48
|
+
# ── Context manager ────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
def __enter__(self) -> Connection:
|
|
51
|
+
return self
|
|
52
|
+
|
|
53
|
+
def __exit__(self, *_: Any) -> None:
|
|
54
|
+
self.close()
|
|
55
|
+
|
|
56
|
+
# ── PEP 249 interface ──────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
def close(self) -> None:
|
|
59
|
+
"""Close the session and release resources."""
|
|
60
|
+
if not self._closed:
|
|
61
|
+
self._closed = True
|
|
62
|
+
try:
|
|
63
|
+
self._client.close_session(self._session_handle)
|
|
64
|
+
except Exception:
|
|
65
|
+
pass
|
|
66
|
+
if self._owns_client:
|
|
67
|
+
self._client.close()
|
|
68
|
+
|
|
69
|
+
def commit(self) -> None:
|
|
70
|
+
"""No-op — Flink SQL Gateway does not support transactions."""
|
|
71
|
+
|
|
72
|
+
def rollback(self) -> None:
|
|
73
|
+
"""Not supported — Flink SQL Gateway does not support transactions."""
|
|
74
|
+
raise NotSupportedError("Flink SQL Gateway does not support transactions")
|
|
75
|
+
|
|
76
|
+
def cursor(self) -> Cursor:
|
|
77
|
+
"""Create a new Cursor bound to this connection."""
|
|
78
|
+
if self._closed:
|
|
79
|
+
raise ProgrammingError("connection is closed")
|
|
80
|
+
return Cursor(self)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def connect(
|
|
84
|
+
url: str,
|
|
85
|
+
*,
|
|
86
|
+
properties: dict[str, str] | None = None,
|
|
87
|
+
api_version: str = "v3",
|
|
88
|
+
) -> Connection:
|
|
89
|
+
"""Open a connection to a Flink SQL Gateway.
|
|
90
|
+
|
|
91
|
+
This is the PEP 249 module-level ``connect()`` function.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
url: Gateway URL, e.g. ``"http://localhost:8083"``.
|
|
95
|
+
properties: Optional session properties.
|
|
96
|
+
api_version: REST API version (default ``"v3"``).
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
A :class:`Connection` instance.
|
|
100
|
+
"""
|
|
101
|
+
client = FlinkSqlGatewayClient(url, api_version=api_version)
|
|
102
|
+
try:
|
|
103
|
+
session_handle = client.open_session(
|
|
104
|
+
OpenSessionRequest(properties=properties) if properties else None
|
|
105
|
+
)
|
|
106
|
+
except Exception:
|
|
107
|
+
client.close()
|
|
108
|
+
raise
|
|
109
|
+
return Connection(client, session_handle, _owns_client=True)
|