mapepire-python 0.1.8__tar.gz → 0.2.0__tar.gz
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.
- {mapepire_python-0.1.8 → mapepire_python-0.2.0}/PKG-INFO +9 -3
- {mapepire_python-0.1.8 → mapepire_python-0.2.0}/README.md +7 -0
- {mapepire_python-0.1.8 → mapepire_python-0.2.0}/mapepire_python/base_job.py +9 -1
- {mapepire_python-0.1.8 → mapepire_python-0.2.0}/mapepire_python/client/query.py +8 -3
- {mapepire_python-0.1.8 → mapepire_python-0.2.0}/mapepire_python/client/sql_job.py +9 -4
- mapepire_python-0.2.0/mapepire_python/client/websocket_client.py +20 -0
- {mapepire_python-0.1.8 → mapepire_python-0.2.0}/mapepire_python/core/cursor.py +2 -3
- mapepire_python-0.2.0/mapepire_python/pool/async_websocket_client.py +18 -0
- {mapepire_python-0.1.8 → mapepire_python-0.2.0}/mapepire_python/pool/pool_job.py +28 -50
- {mapepire_python-0.1.8 → mapepire_python-0.2.0}/mapepire_python/ssl.py +6 -1
- {mapepire_python-0.1.8 → mapepire_python-0.2.0}/mapepire_python/version.py +2 -2
- mapepire_python-0.2.0/mapepire_python/websocket.py +61 -0
- {mapepire_python-0.1.8 → mapepire_python-0.2.0}/mapepire_python.egg-info/PKG-INFO +9 -3
- {mapepire_python-0.1.8 → mapepire_python-0.2.0}/mapepire_python.egg-info/SOURCES.txt +3 -1
- {mapepire_python-0.1.8 → mapepire_python-0.2.0}/mapepire_python.egg-info/requires.txt +1 -2
- {mapepire_python-0.1.8 → mapepire_python-0.2.0}/pyproject.toml +1 -2
- mapepire_python-0.1.8/mapepire_python/client/websocket.py +0 -40
- {mapepire_python-0.1.8 → mapepire_python-0.2.0}/LICENSE +0 -0
- {mapepire_python-0.1.8 → mapepire_python-0.2.0}/mapepire_python/__init__.py +0 -0
- {mapepire_python-0.1.8 → mapepire_python-0.2.0}/mapepire_python/asyncio/__init__.py +0 -0
- {mapepire_python-0.1.8 → mapepire_python-0.2.0}/mapepire_python/asyncio/connection.py +0 -0
- {mapepire_python-0.1.8 → mapepire_python-0.2.0}/mapepire_python/asyncio/cursor.py +0 -0
- {mapepire_python-0.1.8 → mapepire_python-0.2.0}/mapepire_python/asyncio/utils.py +0 -0
- {mapepire_python-0.1.8 → mapepire_python-0.2.0}/mapepire_python/client/__init__.py +0 -0
- {mapepire_python-0.1.8 → mapepire_python-0.2.0}/mapepire_python/core/__init__.py +0 -0
- {mapepire_python-0.1.8 → mapepire_python-0.2.0}/mapepire_python/core/connection.py +0 -0
- {mapepire_python-0.1.8 → mapepire_python-0.2.0}/mapepire_python/core/exceptions.py +0 -0
- {mapepire_python-0.1.8 → mapepire_python-0.2.0}/mapepire_python/core/utils.py +0 -0
- {mapepire_python-0.1.8 → mapepire_python-0.2.0}/mapepire_python/data_types.py +0 -0
- {mapepire_python-0.1.8 → mapepire_python-0.2.0}/mapepire_python/pool/__init__.py +0 -0
- {mapepire_python-0.1.8 → mapepire_python-0.2.0}/mapepire_python/pool/pool_client.py +0 -0
- {mapepire_python-0.1.8 → mapepire_python-0.2.0}/mapepire_python/pool/pool_query.py +0 -0
- {mapepire_python-0.1.8 → mapepire_python-0.2.0}/mapepire_python/py.typed +0 -0
- {mapepire_python-0.1.8 → mapepire_python-0.2.0}/mapepire_python/query_manager.py +0 -0
- {mapepire_python-0.1.8 → mapepire_python-0.2.0}/mapepire_python.egg-info/dependency_links.txt +0 -0
- {mapepire_python-0.1.8 → mapepire_python-0.2.0}/mapepire_python.egg-info/top_level.txt +0 -0
- {mapepire_python-0.1.8 → mapepire_python-0.2.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: mapepire-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Author-email: Adam Shedivy <adam.shedivy@ibm.com>, Irfan Sharif <IrfanSharif@ibm.com>
|
|
5
5
|
License: Apache License
|
|
6
6
|
Version 2.0, January 2004
|
|
@@ -216,8 +216,7 @@ Requires-Python: >3.9
|
|
|
216
216
|
Description-Content-Type: text/markdown
|
|
217
217
|
License-File: LICENSE
|
|
218
218
|
Requires-Dist: dataclasses-json>=0.6.4
|
|
219
|
-
Requires-Dist:
|
|
220
|
-
Requires-Dist: websockets==13.1
|
|
219
|
+
Requires-Dist: websockets>=14.0
|
|
221
220
|
Requires-Dist: pyee
|
|
222
221
|
Requires-Dist: pep249abc
|
|
223
222
|
Provides-Extra: dev
|
|
@@ -315,6 +314,13 @@ Requires-Dist: pre-commit; extra == "dev"
|
|
|
315
314
|
|
|
316
315
|
`mapepire-python` requires Python 3.10 or later.
|
|
317
316
|
|
|
317
|
+
> [!NOTE]
|
|
318
|
+
> New websocket Implementation: As of version 0.2.0, `mapepire-python` uses the `websockets` library for websocket connections. If you are upgrading from a previous version, make sure to update your dependecies. The `websocket-client` library is no longer supported.
|
|
319
|
+
> - To update run `pip install -U mapepire-python`
|
|
320
|
+
>
|
|
321
|
+
> - More info on [websockets](https://websockets.readthedocs.io/en/stable/)
|
|
322
|
+
|
|
323
|
+
|
|
318
324
|
### Install with `pip`
|
|
319
325
|
|
|
320
326
|
`mapepire-python` is available on [PyPi](https://pypi.org/project/mapepire-python/). Just Run
|
|
@@ -69,6 +69,13 @@
|
|
|
69
69
|
|
|
70
70
|
`mapepire-python` requires Python 3.10 or later.
|
|
71
71
|
|
|
72
|
+
> [!NOTE]
|
|
73
|
+
> New websocket Implementation: As of version 0.2.0, `mapepire-python` uses the `websockets` library for websocket connections. If you are upgrading from a previous version, make sure to update your dependecies. The `websocket-client` library is no longer supported.
|
|
74
|
+
> - To update run `pip install -U mapepire-python`
|
|
75
|
+
>
|
|
76
|
+
> - More info on [websockets](https://websockets.readthedocs.io/en/stable/)
|
|
77
|
+
|
|
78
|
+
|
|
72
79
|
### Install with `pip`
|
|
73
80
|
|
|
74
81
|
`mapepire-python` is available on [PyPi](https://pypi.org/project/mapepire-python/). Just Run
|
|
@@ -39,9 +39,17 @@ class BaseJob:
|
|
|
39
39
|
|
|
40
40
|
if not isinstance(db2_server, DaemonServer):
|
|
41
41
|
raise TypeError("db2_server must be of type DaemonServer")
|
|
42
|
-
|
|
42
|
+
self.creds = db2_server
|
|
43
43
|
return db2_server
|
|
44
44
|
|
|
45
|
+
def __str__(self) -> str:
|
|
46
|
+
creds_str = self.creds
|
|
47
|
+
if isinstance(self.creds, DaemonServer):
|
|
48
|
+
creds_dict = self.creds.__dict__.copy()
|
|
49
|
+
creds_dict.pop("password", None) # Remove password if present
|
|
50
|
+
creds_str = str(creds_dict)
|
|
51
|
+
return f"BaseJob(creds={creds_str}, options={self.options})"
|
|
52
|
+
|
|
45
53
|
def connect(
|
|
46
54
|
self, db2_server: Union[DaemonServer, Dict[str, Any], Path], **kwargs
|
|
47
55
|
) -> Dict[str, Any]:
|
|
@@ -2,6 +2,8 @@ import json
|
|
|
2
2
|
from enum import Enum
|
|
3
3
|
from typing import Any, Dict, Generic, List, Optional, TypeVar
|
|
4
4
|
|
|
5
|
+
from mapepire_python.websocket import handle_ws_errors
|
|
6
|
+
|
|
5
7
|
from ..data_types import QueryOptions
|
|
6
8
|
from .sql_job import SQLJob
|
|
7
9
|
|
|
@@ -39,13 +41,14 @@ class Query(Generic[T]):
|
|
|
39
41
|
self.close()
|
|
40
42
|
|
|
41
43
|
def __str__(self):
|
|
42
|
-
return f"Query(sql={self.sql}, parameters={self.parameters}, correlation_id={self._correlation_id})"
|
|
44
|
+
return f"Query(job={str(self.job)}, sql={self.sql}, parameters={self.parameters}, correlation_id={self._correlation_id})"
|
|
43
45
|
|
|
44
46
|
def _execute_query(self, qeury_object: Dict[str, Any]) -> Dict[str, Any]:
|
|
45
47
|
self.job.send(json.dumps(qeury_object))
|
|
46
48
|
query_result: Dict[str, Any] = json.loads(self.job._socket.recv())
|
|
47
49
|
return query_result
|
|
48
50
|
|
|
51
|
+
@handle_ws_errors
|
|
49
52
|
def prepare_sql_execute(self):
|
|
50
53
|
# check Query state first
|
|
51
54
|
if self.state == QueryState.RUN_DONE:
|
|
@@ -67,7 +70,6 @@ class Query(Generic[T]):
|
|
|
67
70
|
)
|
|
68
71
|
|
|
69
72
|
if not query_result.get("success", False) and not self.is_cl_command:
|
|
70
|
-
print(query_result)
|
|
71
73
|
self.state = QueryState.ERROR
|
|
72
74
|
error_keys = ["error", "sql_state", "sql_rc"]
|
|
73
75
|
error_list = {
|
|
@@ -82,6 +84,7 @@ class Query(Generic[T]):
|
|
|
82
84
|
|
|
83
85
|
return query_result
|
|
84
86
|
|
|
87
|
+
@handle_ws_errors
|
|
85
88
|
def run(self, rows_to_fetch: Optional[int] = None) -> Dict[str, Any]:
|
|
86
89
|
if rows_to_fetch is None:
|
|
87
90
|
rows_to_fetch = self._rows_to_fetch
|
|
@@ -135,6 +138,7 @@ class Query(Generic[T]):
|
|
|
135
138
|
|
|
136
139
|
return query_result
|
|
137
140
|
|
|
141
|
+
@handle_ws_errors
|
|
138
142
|
def fetch_more(self, rows_to_fetch: Optional[int] = None) -> Dict[str, Any]:
|
|
139
143
|
if rows_to_fetch is None:
|
|
140
144
|
rows_to_fetch = self._rows_to_fetch
|
|
@@ -169,8 +173,9 @@ class Query(Generic[T]):
|
|
|
169
173
|
|
|
170
174
|
return query_result
|
|
171
175
|
|
|
176
|
+
@handle_ws_errors
|
|
172
177
|
def close(self):
|
|
173
|
-
if not self.job._socket
|
|
178
|
+
if not self.job._socket:
|
|
174
179
|
raise Exception("SQL Job not connected")
|
|
175
180
|
if self._correlation_id and self.state is not QueryState.RUN_DONE:
|
|
176
181
|
self.state = QueryState.RUN_DONE
|
|
@@ -2,11 +2,13 @@ import json
|
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
from typing import Any, Dict, Optional, Union
|
|
4
4
|
|
|
5
|
-
from
|
|
5
|
+
from websockets.sync.client import ClientConnection
|
|
6
|
+
|
|
7
|
+
from mapepire_python.client.websocket_client import WebsocketConnection
|
|
8
|
+
from mapepire_python.websocket import handle_ws_errors
|
|
6
9
|
|
|
7
10
|
from ..base_job import BaseJob
|
|
8
11
|
from ..data_types import DaemonServer, JobStatus, QueryOptions
|
|
9
|
-
from .websocket import WebsocketConnection
|
|
10
12
|
|
|
11
13
|
__all__ = ["SQLJob"]
|
|
12
14
|
|
|
@@ -49,7 +51,7 @@ class SQLJob(BaseJob):
|
|
|
49
51
|
self._unique_id_counter += 1
|
|
50
52
|
return f"{prefix}{self._unique_id_counter}"
|
|
51
53
|
|
|
52
|
-
def _get_channel(self, db2_server: DaemonServer) ->
|
|
54
|
+
def _get_channel(self, db2_server: DaemonServer) -> ClientConnection:
|
|
53
55
|
"""returns a websocket connection to the mapepire server
|
|
54
56
|
|
|
55
57
|
Args:
|
|
@@ -69,6 +71,7 @@ class SQLJob(BaseJob):
|
|
|
69
71
|
"""
|
|
70
72
|
return self._status
|
|
71
73
|
|
|
74
|
+
@handle_ws_errors
|
|
72
75
|
def send(self, content: str) -> None:
|
|
73
76
|
"""sends content to the mapepire server
|
|
74
77
|
|
|
@@ -77,6 +80,7 @@ class SQLJob(BaseJob):
|
|
|
77
80
|
"""
|
|
78
81
|
self._socket.send(content)
|
|
79
82
|
|
|
83
|
+
@handle_ws_errors
|
|
80
84
|
def connect(
|
|
81
85
|
self, db2_server: Union[DaemonServer, Dict[str, Any], Path], **kwargs
|
|
82
86
|
) -> Dict[Any, Any]:
|
|
@@ -93,7 +97,7 @@ class SQLJob(BaseJob):
|
|
|
93
97
|
"""
|
|
94
98
|
db2_server = self._parse_connection_input(db2_server, **kwargs)
|
|
95
99
|
|
|
96
|
-
self._socket:
|
|
100
|
+
self._socket: ClientConnection = self._get_channel(db2_server)
|
|
97
101
|
|
|
98
102
|
props = ";".join(
|
|
99
103
|
[
|
|
@@ -164,6 +168,7 @@ class SQLJob(BaseJob):
|
|
|
164
168
|
|
|
165
169
|
return Query(job=self, query=sql, opts=query_options)
|
|
166
170
|
|
|
171
|
+
@handle_ws_errors
|
|
167
172
|
def query_and_run(
|
|
168
173
|
self, sql: str, opts: Optional[Dict[str, Any]] = None, **kwargs
|
|
169
174
|
) -> Dict[str, Any]:
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from websockets.sync.client import ClientConnection, connect
|
|
2
|
+
|
|
3
|
+
from mapepire_python.websocket import BaseConnection, handle_ws_errors
|
|
4
|
+
|
|
5
|
+
from ..data_types import DaemonServer
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class WebsocketConnection(BaseConnection):
|
|
9
|
+
|
|
10
|
+
def __init__(self, db2_server: DaemonServer) -> None:
|
|
11
|
+
super().__init__(db2_server)
|
|
12
|
+
|
|
13
|
+
@handle_ws_errors
|
|
14
|
+
def connect(self) -> ClientConnection:
|
|
15
|
+
return connect(
|
|
16
|
+
self.uri,
|
|
17
|
+
additional_headers=self.headers,
|
|
18
|
+
open_timeout=10,
|
|
19
|
+
ssl=self._create_ssl_context(self.db2_server),
|
|
20
|
+
)
|
|
@@ -37,7 +37,7 @@ class Cursor(pep249.CursorConnectionMixin, pep249.IterableCursorMixin, pep249.Tr
|
|
|
37
37
|
self._connection = weakref.proxy(connection)
|
|
38
38
|
self.job = job
|
|
39
39
|
self.query: Query = None
|
|
40
|
-
self.query_q = deque(maxlen=20)
|
|
40
|
+
self.query_q: deque[Query] = deque(maxlen=20)
|
|
41
41
|
self.__closed = False
|
|
42
42
|
self.__has_results = False
|
|
43
43
|
|
|
@@ -102,7 +102,6 @@ class Cursor(pep249.CursorConnectionMixin, pep249.IterableCursorMixin, pep249.Tr
|
|
|
102
102
|
query = Query(self.job, operation, create_opts)
|
|
103
103
|
|
|
104
104
|
prepare_result = query.prepare_sql_execute()
|
|
105
|
-
# print(prepare_result)
|
|
106
105
|
|
|
107
106
|
if prepare_result["has_results"]:
|
|
108
107
|
self.query = query
|
|
@@ -188,7 +187,7 @@ class Cursor(pep249.CursorConnectionMixin, pep249.IterableCursorMixin, pep249.Tr
|
|
|
188
187
|
def close(self) -> None:
|
|
189
188
|
if self._closed:
|
|
190
189
|
return
|
|
191
|
-
if self.query
|
|
190
|
+
if self.query:
|
|
192
191
|
for q in self.query_q:
|
|
193
192
|
q.close()
|
|
194
193
|
self.query_q.clear()
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from websockets.asyncio.client import ClientConnection, connect
|
|
2
|
+
|
|
3
|
+
from mapepire_python.websocket import BaseConnection, handle_ws_errors
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AsyncWebSocketConnection(BaseConnection):
|
|
7
|
+
def __init__(self, db2_server):
|
|
8
|
+
super().__init__(db2_server)
|
|
9
|
+
|
|
10
|
+
@handle_ws_errors
|
|
11
|
+
async def connect(self) -> ClientConnection:
|
|
12
|
+
websocket = await connect(
|
|
13
|
+
self.uri,
|
|
14
|
+
additional_headers=self.headers,
|
|
15
|
+
open_timeout=10,
|
|
16
|
+
ssl=self._create_ssl_context(self.db2_server),
|
|
17
|
+
)
|
|
18
|
+
return websocket
|
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
import base64
|
|
3
2
|
import json
|
|
4
|
-
import
|
|
3
|
+
import logging
|
|
5
4
|
from pathlib import Path
|
|
6
5
|
from typing import Any, Dict, Optional, Union
|
|
7
6
|
|
|
8
7
|
import websockets
|
|
9
8
|
from pyee.asyncio import AsyncIOEventEmitter
|
|
9
|
+
from websockets.asyncio.client import ClientConnection
|
|
10
|
+
|
|
11
|
+
from mapepire_python.pool.async_websocket_client import AsyncWebSocketConnection
|
|
10
12
|
|
|
11
13
|
from ..base_job import BaseJob
|
|
12
14
|
from ..data_types import DaemonServer, JobStatus, QueryOptions
|
|
13
15
|
|
|
14
16
|
__all__ = ["PoolJob"]
|
|
15
17
|
|
|
18
|
+
logger = logging.getLogger("websockets.client")
|
|
19
|
+
|
|
16
20
|
|
|
17
21
|
class PoolJob(BaseJob):
|
|
18
22
|
unique_id_counter = 0
|
|
@@ -64,11 +68,7 @@ class PoolJob(BaseJob):
|
|
|
64
68
|
def enable_local_channel_trace(self):
|
|
65
69
|
self.is_tracing_channel_data = True
|
|
66
70
|
|
|
67
|
-
def
|
|
68
|
-
if level:
|
|
69
|
-
print(message, flush=True)
|
|
70
|
-
|
|
71
|
-
async def get_channel(self, db2_server: DaemonServer) -> websockets.WebSocketClientProtocol:
|
|
71
|
+
async def get_channel(self, db2_server: DaemonServer) -> ClientConnection:
|
|
72
72
|
"""returns a websocket connection to the mapepire server
|
|
73
73
|
|
|
74
74
|
Args:
|
|
@@ -81,26 +81,8 @@ class PoolJob(BaseJob):
|
|
|
81
81
|
Returns:
|
|
82
82
|
websockets.WebSocketClientProtocol: websocket connection
|
|
83
83
|
"""
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
"Authorization": "Basic "
|
|
87
|
-
+ base64.b64encode(f"{db2_server.user}:{db2_server.password}".encode()).decode("ascii")
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
ssl_contest = ssl.create_default_context(cafile=db2_server.ca)
|
|
91
|
-
ssl_contest.check_hostname = False
|
|
92
|
-
ssl_contest.verify_mode = ssl.CERT_NONE
|
|
93
|
-
|
|
94
|
-
try:
|
|
95
|
-
socket = await websockets.connect(
|
|
96
|
-
uri=uri, extra_headers=headers, ssl=ssl_contest, ping_timeout=None, open_timeout=30
|
|
97
|
-
)
|
|
98
|
-
except TimeoutError as e:
|
|
99
|
-
raise TimeoutError("Failed to connect to server") from e
|
|
100
|
-
except Exception as e:
|
|
101
|
-
raise e
|
|
102
|
-
|
|
103
|
-
return socket
|
|
84
|
+
socket = AsyncWebSocketConnection(db2_server)
|
|
85
|
+
return await socket.connect()
|
|
104
86
|
|
|
105
87
|
async def send(self, content: str) -> Dict[Any, Any]:
|
|
106
88
|
"""sends content to the mapepire server
|
|
@@ -111,18 +93,21 @@ class PoolJob(BaseJob):
|
|
|
111
93
|
Returns:
|
|
112
94
|
str: response from the server
|
|
113
95
|
"""
|
|
114
|
-
|
|
96
|
+
logger.debug(f"sending data: {content}")
|
|
115
97
|
|
|
116
98
|
req = json.loads(content)
|
|
117
99
|
if self.socket is None:
|
|
118
100
|
raise RuntimeError("Socket is not connected")
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
101
|
+
try:
|
|
102
|
+
await self.socket.send(content)
|
|
103
|
+
self.status = JobStatus.Busy
|
|
104
|
+
logger.debug("waiting for response ...")
|
|
105
|
+
response = await self.wait_for_response(req["id"])
|
|
106
|
+
# logger.debug(f"received response: {response}")
|
|
107
|
+
self.status = JobStatus.Ready if self.get_running_count() == 0 else JobStatus.Busy
|
|
108
|
+
return response # type: ignore
|
|
109
|
+
except Exception as e:
|
|
110
|
+
raise e
|
|
126
111
|
|
|
127
112
|
async def wait_for_response(self, req_id: str) -> str:
|
|
128
113
|
"""when a request is sent to the server, this method waits for the response
|
|
@@ -139,16 +124,14 @@ class PoolJob(BaseJob):
|
|
|
139
124
|
future = asyncio.Future()
|
|
140
125
|
|
|
141
126
|
def on_response(response):
|
|
142
|
-
|
|
143
|
-
self.enable_local_trace, f"Received response for req_id: {req_id} - {response}"
|
|
144
|
-
)
|
|
127
|
+
logger.debug(f"Received response for req_id: {req_id} - {response}")
|
|
145
128
|
if not future.done():
|
|
146
129
|
future.set_result(response)
|
|
147
130
|
self.response_emitter.remove_listener(req_id, on_response)
|
|
148
131
|
|
|
149
132
|
try:
|
|
150
133
|
self.response_emitter.on(req_id, on_response)
|
|
151
|
-
|
|
134
|
+
logger.debug(f"Listener registered for req_id: {req_id}")
|
|
152
135
|
return await future
|
|
153
136
|
except Exception as e:
|
|
154
137
|
self.response_emitter.remove_listener(req_id, on_response)
|
|
@@ -158,9 +141,8 @@ class PoolJob(BaseJob):
|
|
|
158
141
|
return self.status
|
|
159
142
|
|
|
160
143
|
def get_running_count(self) -> int:
|
|
161
|
-
|
|
162
|
-
self.
|
|
163
|
-
f"--- running count {self.unique_id}: {len(self.response_emitter.event_names())}, status: {self.get_status()}",
|
|
144
|
+
logger.debug(
|
|
145
|
+
f"--- running count {self.unique_id}: {len(self.response_emitter.event_names())}, status: {self.get_status()}"
|
|
164
146
|
)
|
|
165
147
|
return len(self.response_emitter.event_names())
|
|
166
148
|
|
|
@@ -235,25 +217,21 @@ class PoolJob(BaseJob):
|
|
|
235
217
|
if self.socket is None:
|
|
236
218
|
raise RuntimeError("Socket is not connected")
|
|
237
219
|
async for message in self.socket:
|
|
238
|
-
|
|
220
|
+
logger.debug(f"Received raw message: {message}")
|
|
239
221
|
|
|
240
222
|
try:
|
|
241
223
|
response = json.loads(message)
|
|
242
224
|
req_id = response.get("id")
|
|
243
225
|
if req_id:
|
|
244
|
-
|
|
245
|
-
self.enable_local_trace, f"Emitting response for req_id: {req_id}"
|
|
246
|
-
)
|
|
226
|
+
logger.debug(f"Emitting response for req_id: {req_id}")
|
|
247
227
|
self.response_emitter.emit(req_id, response)
|
|
248
228
|
else:
|
|
249
|
-
|
|
250
|
-
self.enable_local_trace, f"No req_id found in response: {response}"
|
|
251
|
-
)
|
|
229
|
+
logger.debug(f"No req_id found in response: {response}")
|
|
252
230
|
except json.JSONDecodeError as e:
|
|
253
231
|
raise ValueError(f"Error decoding JSON: {e}")
|
|
254
232
|
except Exception as e:
|
|
255
233
|
raise RuntimeError(f"Error: {e}")
|
|
256
|
-
except websockets.exceptions.
|
|
234
|
+
except websockets.exceptions.ConnectionClosedError:
|
|
257
235
|
await self.dispose()
|
|
258
236
|
|
|
259
237
|
def query(
|
|
@@ -16,4 +16,9 @@ def get_certificate(creds: DaemonServer) -> Optional[bytes]:
|
|
|
16
16
|
)
|
|
17
17
|
with socket.create_connection((creds.host, creds.port)) as sock:
|
|
18
18
|
with context.wrap_socket(sock, server_hostname=creds.host) as ssock:
|
|
19
|
-
|
|
19
|
+
try:
|
|
20
|
+
ssock.do_handshake()
|
|
21
|
+
cert = ssock.getpeercert(binary_form=True)
|
|
22
|
+
return ssl.DER_cert_to_PEM_cert(cert)
|
|
23
|
+
except ssl.SSLError as er:
|
|
24
|
+
raise er
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
_MAJOR = "0"
|
|
2
|
-
_MINOR = "
|
|
2
|
+
_MINOR = "2"
|
|
3
3
|
# On main and in a nightly release the patch should be one ahead of the last
|
|
4
4
|
# released build.
|
|
5
|
-
_PATCH = "
|
|
5
|
+
_PATCH = "0"
|
|
6
6
|
# This is mainly for nightly builds which have the suffix ".dev$DATE". See
|
|
7
7
|
# https://semver.org/#is-v123-a-semantic-version for the semantics.
|
|
8
8
|
_SUFFIX = ""
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import ssl
|
|
3
|
+
from functools import wraps
|
|
4
|
+
from typing import Any, Callable, TypeVar
|
|
5
|
+
|
|
6
|
+
from websockets import ConcurrencyError, ConnectionClosed, InvalidHandshake, InvalidURI
|
|
7
|
+
|
|
8
|
+
from mapepire_python.data_types import DaemonServer
|
|
9
|
+
|
|
10
|
+
ReturnType = TypeVar("ReturnType")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BaseConnection:
|
|
14
|
+
def __init__(self, db2_server: DaemonServer) -> None:
|
|
15
|
+
self.uri = f"wss://{db2_server.host}:{db2_server.port}/db/"
|
|
16
|
+
self.headers = {
|
|
17
|
+
"Authorization": "Basic "
|
|
18
|
+
+ base64.b64encode(f"{db2_server.user}:{db2_server.password}".encode()).decode("ascii")
|
|
19
|
+
}
|
|
20
|
+
self.db2_server = db2_server
|
|
21
|
+
|
|
22
|
+
def _create_ssl_context(self, db2_server: DaemonServer):
|
|
23
|
+
ssl_context = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH)
|
|
24
|
+
if db2_server.ignoreUnauthorized:
|
|
25
|
+
ssl_context.check_hostname = False
|
|
26
|
+
ssl_context.verify_mode = ssl.CERT_NONE
|
|
27
|
+
elif db2_server.ca:
|
|
28
|
+
ssl_context.load_verify_locations(cadata=db2_server.ca)
|
|
29
|
+
return ssl_context
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _parse_ws_error(error: Exception, driver: Any = None):
|
|
33
|
+
to_str = str(driver)
|
|
34
|
+
|
|
35
|
+
if isinstance(error, InvalidURI):
|
|
36
|
+
raise InvalidURI(f"The provided URI is not a valid WebSocket URI: {to_str}")
|
|
37
|
+
elif isinstance(error, OSError):
|
|
38
|
+
raise OSError(f"The TCP connection failed to connect to Mapepire server: {to_str}")
|
|
39
|
+
elif isinstance(error, InvalidHandshake):
|
|
40
|
+
raise InvalidHandshake("The opening handshake failed.")
|
|
41
|
+
elif isinstance(error, TimeoutError):
|
|
42
|
+
raise TimeoutError("The opening handshake timed out.")
|
|
43
|
+
elif isinstance(error, ConnectionClosed):
|
|
44
|
+
raise ConnectionClosed("The Conection was closed.")
|
|
45
|
+
elif isinstance(error, ConcurrencyError):
|
|
46
|
+
raise ConcurrencyError("Connection is sending a fragmented message")
|
|
47
|
+
elif isinstance(error, TypeError):
|
|
48
|
+
raise TypeError("Message doesn't have a supported type")
|
|
49
|
+
else:
|
|
50
|
+
return error
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def handle_ws_errors(function: Callable[..., ReturnType]) -> Callable[..., ReturnType]:
|
|
54
|
+
@wraps(function)
|
|
55
|
+
def _impl(self, *args, **kwargs):
|
|
56
|
+
try:
|
|
57
|
+
return function(self, *args, **kwargs)
|
|
58
|
+
except RuntimeError as err:
|
|
59
|
+
raise _parse_ws_error(err, driver=self) from err
|
|
60
|
+
|
|
61
|
+
return _impl
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: mapepire-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Author-email: Adam Shedivy <adam.shedivy@ibm.com>, Irfan Sharif <IrfanSharif@ibm.com>
|
|
5
5
|
License: Apache License
|
|
6
6
|
Version 2.0, January 2004
|
|
@@ -216,8 +216,7 @@ Requires-Python: >3.9
|
|
|
216
216
|
Description-Content-Type: text/markdown
|
|
217
217
|
License-File: LICENSE
|
|
218
218
|
Requires-Dist: dataclasses-json>=0.6.4
|
|
219
|
-
Requires-Dist:
|
|
220
|
-
Requires-Dist: websockets==13.1
|
|
219
|
+
Requires-Dist: websockets>=14.0
|
|
221
220
|
Requires-Dist: pyee
|
|
222
221
|
Requires-Dist: pep249abc
|
|
223
222
|
Provides-Extra: dev
|
|
@@ -315,6 +314,13 @@ Requires-Dist: pre-commit; extra == "dev"
|
|
|
315
314
|
|
|
316
315
|
`mapepire-python` requires Python 3.10 or later.
|
|
317
316
|
|
|
317
|
+
> [!NOTE]
|
|
318
|
+
> New websocket Implementation: As of version 0.2.0, `mapepire-python` uses the `websockets` library for websocket connections. If you are upgrading from a previous version, make sure to update your dependecies. The `websocket-client` library is no longer supported.
|
|
319
|
+
> - To update run `pip install -U mapepire-python`
|
|
320
|
+
>
|
|
321
|
+
> - More info on [websockets](https://websockets.readthedocs.io/en/stable/)
|
|
322
|
+
|
|
323
|
+
|
|
318
324
|
### Install with `pip`
|
|
319
325
|
|
|
320
326
|
`mapepire-python` is available on [PyPi](https://pypi.org/project/mapepire-python/). Just Run
|
|
@@ -8,6 +8,7 @@ mapepire_python/py.typed
|
|
|
8
8
|
mapepire_python/query_manager.py
|
|
9
9
|
mapepire_python/ssl.py
|
|
10
10
|
mapepire_python/version.py
|
|
11
|
+
mapepire_python/websocket.py
|
|
11
12
|
mapepire_python.egg-info/PKG-INFO
|
|
12
13
|
mapepire_python.egg-info/SOURCES.txt
|
|
13
14
|
mapepire_python.egg-info/dependency_links.txt
|
|
@@ -20,13 +21,14 @@ mapepire_python/asyncio/utils.py
|
|
|
20
21
|
mapepire_python/client/__init__.py
|
|
21
22
|
mapepire_python/client/query.py
|
|
22
23
|
mapepire_python/client/sql_job.py
|
|
23
|
-
mapepire_python/client/
|
|
24
|
+
mapepire_python/client/websocket_client.py
|
|
24
25
|
mapepire_python/core/__init__.py
|
|
25
26
|
mapepire_python/core/connection.py
|
|
26
27
|
mapepire_python/core/cursor.py
|
|
27
28
|
mapepire_python/core/exceptions.py
|
|
28
29
|
mapepire_python/core/utils.py
|
|
29
30
|
mapepire_python/pool/__init__.py
|
|
31
|
+
mapepire_python/pool/async_websocket_client.py
|
|
30
32
|
mapepire_python/pool/pool_client.py
|
|
31
33
|
mapepire_python/pool/pool_job.py
|
|
32
34
|
mapepire_python/pool/pool_query.py
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import base64
|
|
2
|
-
import ssl
|
|
3
|
-
from typing import Any, Dict
|
|
4
|
-
|
|
5
|
-
from websocket import WebSocket, create_connection
|
|
6
|
-
|
|
7
|
-
from ..data_types import DaemonServer
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class WebsocketConnection:
|
|
11
|
-
def __init__(self, db2_server: DaemonServer) -> None:
|
|
12
|
-
self.uri = f"wss://{db2_server.host}:{db2_server.port}/db/"
|
|
13
|
-
self.headers = {
|
|
14
|
-
"Authorization": "Basic "
|
|
15
|
-
+ base64.b64encode(f"{db2_server.user}:{db2_server.password}".encode()).decode("ascii")
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
self.ssl_opts = self._build_ssl_options(db2_server)
|
|
19
|
-
self.db2_server = db2_server
|
|
20
|
-
|
|
21
|
-
def _build_ssl_options(self, db2_server: DaemonServer) -> Dict[str, Any]:
|
|
22
|
-
ssl_opts: Dict[str, Any] = {}
|
|
23
|
-
if db2_server.ignoreUnauthorized:
|
|
24
|
-
ssl_opts["cert_reqs"] = ssl.CERT_NONE
|
|
25
|
-
if db2_server.ca:
|
|
26
|
-
ssl_context = ssl.create_default_context(cadata=db2_server.ca)
|
|
27
|
-
ssl_context.check_hostname = False
|
|
28
|
-
ssl_opts["ssl_context"] = ssl_context
|
|
29
|
-
ssl_opts["cert_reqs"] = ssl.CERT_NONE
|
|
30
|
-
return ssl_opts
|
|
31
|
-
|
|
32
|
-
def connect(self) -> WebSocket:
|
|
33
|
-
try:
|
|
34
|
-
return create_connection(
|
|
35
|
-
self.uri, header=self.headers, sslopt=self.ssl_opts, timeout=10
|
|
36
|
-
)
|
|
37
|
-
except Exception:
|
|
38
|
-
raise RuntimeError(
|
|
39
|
-
f"An error occurred while connecting to the server: {self.db2_server.host}"
|
|
40
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mapepire_python-0.1.8 → mapepire_python-0.2.0}/mapepire_python.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|