mapepire-python 0.1.7__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.
Files changed (37) hide show
  1. {mapepire_python-0.1.7 → mapepire_python-0.2.0}/PKG-INFO +10 -4
  2. {mapepire_python-0.1.7 → mapepire_python-0.2.0}/README.md +7 -0
  3. {mapepire_python-0.1.7 → mapepire_python-0.2.0}/mapepire_python/base_job.py +9 -1
  4. {mapepire_python-0.1.7 → mapepire_python-0.2.0}/mapepire_python/client/query.py +8 -3
  5. {mapepire_python-0.1.7 → mapepire_python-0.2.0}/mapepire_python/client/sql_job.py +12 -5
  6. mapepire_python-0.2.0/mapepire_python/client/websocket_client.py +20 -0
  7. {mapepire_python-0.1.7 → mapepire_python-0.2.0}/mapepire_python/core/connection.py +2 -3
  8. {mapepire_python-0.1.7 → mapepire_python-0.2.0}/mapepire_python/core/cursor.py +2 -3
  9. mapepire_python-0.2.0/mapepire_python/pool/async_websocket_client.py +18 -0
  10. {mapepire_python-0.1.7 → mapepire_python-0.2.0}/mapepire_python/pool/pool_job.py +28 -50
  11. {mapepire_python-0.1.7 → mapepire_python-0.2.0}/mapepire_python/ssl.py +6 -1
  12. {mapepire_python-0.1.7 → mapepire_python-0.2.0}/mapepire_python/version.py +2 -2
  13. mapepire_python-0.2.0/mapepire_python/websocket.py +61 -0
  14. {mapepire_python-0.1.7 → mapepire_python-0.2.0}/mapepire_python.egg-info/PKG-INFO +10 -4
  15. {mapepire_python-0.1.7 → mapepire_python-0.2.0}/mapepire_python.egg-info/SOURCES.txt +3 -1
  16. {mapepire_python-0.1.7 → mapepire_python-0.2.0}/mapepire_python.egg-info/requires.txt +1 -2
  17. {mapepire_python-0.1.7 → mapepire_python-0.2.0}/pyproject.toml +2 -3
  18. mapepire_python-0.1.7/mapepire_python/client/websocket.py +0 -37
  19. {mapepire_python-0.1.7 → mapepire_python-0.2.0}/LICENSE +0 -0
  20. {mapepire_python-0.1.7 → mapepire_python-0.2.0}/mapepire_python/__init__.py +0 -0
  21. {mapepire_python-0.1.7 → mapepire_python-0.2.0}/mapepire_python/asyncio/__init__.py +0 -0
  22. {mapepire_python-0.1.7 → mapepire_python-0.2.0}/mapepire_python/asyncio/connection.py +0 -0
  23. {mapepire_python-0.1.7 → mapepire_python-0.2.0}/mapepire_python/asyncio/cursor.py +0 -0
  24. {mapepire_python-0.1.7 → mapepire_python-0.2.0}/mapepire_python/asyncio/utils.py +0 -0
  25. {mapepire_python-0.1.7 → mapepire_python-0.2.0}/mapepire_python/client/__init__.py +0 -0
  26. {mapepire_python-0.1.7 → mapepire_python-0.2.0}/mapepire_python/core/__init__.py +0 -0
  27. {mapepire_python-0.1.7 → mapepire_python-0.2.0}/mapepire_python/core/exceptions.py +0 -0
  28. {mapepire_python-0.1.7 → mapepire_python-0.2.0}/mapepire_python/core/utils.py +0 -0
  29. {mapepire_python-0.1.7 → mapepire_python-0.2.0}/mapepire_python/data_types.py +0 -0
  30. {mapepire_python-0.1.7 → mapepire_python-0.2.0}/mapepire_python/pool/__init__.py +0 -0
  31. {mapepire_python-0.1.7 → mapepire_python-0.2.0}/mapepire_python/pool/pool_client.py +0 -0
  32. {mapepire_python-0.1.7 → mapepire_python-0.2.0}/mapepire_python/pool/pool_query.py +0 -0
  33. {mapepire_python-0.1.7 → mapepire_python-0.2.0}/mapepire_python/py.typed +0 -0
  34. {mapepire_python-0.1.7 → mapepire_python-0.2.0}/mapepire_python/query_manager.py +0 -0
  35. {mapepire_python-0.1.7 → mapepire_python-0.2.0}/mapepire_python.egg-info/dependency_links.txt +0 -0
  36. {mapepire_python-0.1.7 → mapepire_python-0.2.0}/mapepire_python.egg-info/top_level.txt +0 -0
  37. {mapepire_python-0.1.7 → 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.1.7
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
@@ -212,12 +212,11 @@ Classifier: Development Status :: 3 - Alpha
212
212
  Classifier: License :: OSI Approved :: Apache Software License
213
213
  Classifier: Programming Language :: Python :: 3
214
214
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
215
- Requires-Python: >=3.9
215
+ 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: websocket-client>=1.2.1
220
- Requires-Dist: websockets
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.connected:
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 websocket import WebSocket
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
 
@@ -19,6 +21,7 @@ class SQLJob(BaseJob):
19
21
  **kwargs,
20
22
  ) -> None:
21
23
  super().__init__(creds, options, **kwargs)
24
+ self._socket = None
22
25
  self._unique_id_counter: int = 0
23
26
  self._reponse_emitter = {}
24
27
  self._status: JobStatus = JobStatus.NotStarted
@@ -48,7 +51,7 @@ class SQLJob(BaseJob):
48
51
  self._unique_id_counter += 1
49
52
  return f"{prefix}{self._unique_id_counter}"
50
53
 
51
- def _get_channel(self, db2_server: DaemonServer) -> WebSocket:
54
+ def _get_channel(self, db2_server: DaemonServer) -> ClientConnection:
52
55
  """returns a websocket connection to the mapepire server
53
56
 
54
57
  Args:
@@ -68,6 +71,7 @@ class SQLJob(BaseJob):
68
71
  """
69
72
  return self._status
70
73
 
74
+ @handle_ws_errors
71
75
  def send(self, content: str) -> None:
72
76
  """sends content to the mapepire server
73
77
 
@@ -76,6 +80,7 @@ class SQLJob(BaseJob):
76
80
  """
77
81
  self._socket.send(content)
78
82
 
83
+ @handle_ws_errors
79
84
  def connect(
80
85
  self, db2_server: Union[DaemonServer, Dict[str, Any], Path], **kwargs
81
86
  ) -> Dict[Any, Any]:
@@ -92,7 +97,7 @@ class SQLJob(BaseJob):
92
97
  """
93
98
  db2_server = self._parse_connection_input(db2_server, **kwargs)
94
99
 
95
- self._socket: WebSocket = self._get_channel(db2_server)
100
+ self._socket: ClientConnection = self._get_channel(db2_server)
96
101
 
97
102
  props = ";".join(
98
103
  [
@@ -163,6 +168,7 @@ class SQLJob(BaseJob):
163
168
 
164
169
  return Query(job=self, query=sql, opts=query_options)
165
170
 
171
+ @handle_ws_errors
166
172
  def query_and_run(
167
173
  self, sql: str, opts: Optional[Dict[str, Any]] = None, **kwargs
168
174
  ) -> Dict[str, Any]:
@@ -186,4 +192,5 @@ class SQLJob(BaseJob):
186
192
 
187
193
  def close(self) -> None:
188
194
  self._status = JobStatus.Ended
189
- self._socket.close()
195
+ if self._socket:
196
+ self._socket.close()
@@ -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
+ )
@@ -25,7 +25,6 @@ class Connection(pep249.CursorExecuteMixin, pep249.ConcreteErrorMixin, pep249.Co
25
25
  or a `DaemonServer` object:
26
26
 
27
27
  ```
28
- import asyncio
29
28
  from mapepire_python import connect
30
29
  from mapepire_python.data_types import DaemonServer
31
30
  creds = DaemonServer(
@@ -45,9 +44,9 @@ class Connection(pep249.CursorExecuteMixin, pep249.ConcreteErrorMixin, pep249.Co
45
44
  @convert_runtime_errors
46
45
  def __init__(self, database: Union[DaemonServer, dict, Path], opts={}, **kwargs) -> None:
47
46
  super().__init__()
48
- self.job = SQLJob(creds=database, options=opts, **kwargs)
49
- self.job.connect(database)
50
47
  self._closed = False
48
+ self.job = SQLJob(creds=database, options=opts, **kwargs)
49
+ self.job.connect(database, **kwargs)
51
50
 
52
51
  @raise_if_closed
53
52
  @convert_runtime_errors
@@ -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 and self.job._socket.connected:
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 ssl
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 _local_log(self, level: bool, message: str) -> None:
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
- uri = f"wss://{db2_server.host}:{db2_server.port}/db/"
85
- headers = {
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
- self._local_log(self.enable_local_trace, f"sending data: {content}")
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
- await self.socket.send(content)
120
- self.status = JobStatus.Busy
121
- self._local_log(self.enable_local_trace, "wating for response ...")
122
- response = await self.wait_for_response(req["id"])
123
- self._local_log(self.enable_local_trace, f"recieved response: {response}")
124
- self.status = JobStatus.Ready if self.get_running_count() == 0 else JobStatus.Busy
125
- return response # type: ignore
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
- self._local_log(
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
- self._local_log(self.enable_local_trace, f"Listener registered for req_id: {req_id}")
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
- self._local_log(
162
- self.enable_local_trace,
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
- self._local_log(self.enable_local_trace, f"Received raw message: {message}")
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
- self._local_log(
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
- self._local_log(
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.ConnectionClosed:
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
- return ssock.getpeercert(binary_form=True)
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 = "1"
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 = "7"
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.1.7
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
@@ -212,12 +212,11 @@ Classifier: Development Status :: 3 - Alpha
212
212
  Classifier: License :: OSI Approved :: Apache Software License
213
213
  Classifier: Programming Language :: Python :: 3
214
214
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
215
- Requires-Python: >=3.9
215
+ 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: websocket-client>=1.2.1
220
- Requires-Dist: websockets
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/websocket.py
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,6 +1,5 @@
1
1
  dataclasses-json>=0.6.4
2
- websocket-client>=1.2.1
3
- websockets
2
+ websockets>=14.0
4
3
  pyee
5
4
  pep249abc
6
5
 
@@ -18,11 +18,10 @@ authors = [
18
18
  {name = "Adam Shedivy", email = "adam.shedivy@ibm.com"},
19
19
  {name = "Irfan Sharif", email = "IrfanSharif@ibm.com"}
20
20
  ]
21
- requires-python = ">=3.9"
21
+ requires-python = ">3.9"
22
22
  dependencies = [
23
23
  "dataclasses-json>=0.6.4",
24
- "websocket-client>=1.2.1",
25
- "websockets",
24
+ "websockets>=14.0",
26
25
  "pyee",
27
26
  "pep249abc"
28
27
  ]
@@ -1,37 +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
-
20
- def _build_ssl_options(self, db2_server: DaemonServer) -> Dict[str, Any]:
21
- ssl_opts: Dict[str, Any] = {}
22
- if db2_server.ignoreUnauthorized:
23
- ssl_opts["cert_reqs"] = ssl.CERT_NONE
24
- if db2_server.ca:
25
- ssl_context = ssl.create_default_context(cadata=db2_server.ca)
26
- ssl_context.check_hostname = False
27
- ssl_opts["ssl_context"] = ssl_context
28
- ssl_opts["cert_reqs"] = ssl.CERT_NONE
29
- return ssl_opts
30
-
31
- def connect(self) -> WebSocket:
32
- try:
33
- return create_connection(
34
- self.uri, header=self.headers, sslopt=self.ssl_opts, timeout=10
35
- )
36
- except Exception as e:
37
- raise RuntimeError(f"An error occurred while connecting to the server: {e}")
File without changes