tinytoolslib 0.5.0__tar.gz → 0.6.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.
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tinytoolslib
3
- Version: 0.5.0
4
- Summary: Set of tools for use with Tinycontrol devices like LK2.X, LK3.X, LK4.X or tcPDU.
3
+ Version: 0.6.0
4
+ Summary: Set of tools for use with tinycontrol devices like LK2.X, LK3.X, LK4.X or tcPDU.
5
5
  Author-email: Bartek Barszczewski <tinycontrol.software@gmail.com>
6
6
  License-Expression: Apache-2.0
7
7
  Keywords: tinycontrol,lk,tcpdu
@@ -12,7 +12,7 @@ Description-Content-Type: text/markdown
12
12
  Requires-Dist: aiohttp<4,>=3.9.3
13
13
  Requires-Dist: netifaces<1,>=0.11.0
14
14
  Requires-Dist: requests<3,>=2.31.0
15
- Requires-Dist: tftpy<1,>=0.8.2
15
+ Requires-Dist: tftpy<=0.8.5,>=0.8.2
16
16
 
17
17
  # tinyToolsLib
18
18
 
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tinytoolslib"
7
- version = "0.5.0"
7
+ version = "0.6.0"
8
8
  authors = [
9
9
  { name="Bartek Barszczewski", email="tinycontrol.software@gmail.com" },
10
10
  ]
11
11
  license = "Apache-2.0"
12
- description = "Set of tools for use with Tinycontrol devices like LK2.X, LK3.X, LK4.X or tcPDU."
12
+ description = "Set of tools for use with tinycontrol devices like LK2.X, LK3.X, LK4.X or tcPDU."
13
13
  requires-python = ">=3.7"
14
14
  classifiers = [
15
15
  "Programming Language :: Python :: 3",
@@ -19,7 +19,7 @@ dependencies = [
19
19
  "aiohttp >= 3.9.3, < 4",
20
20
  "netifaces >= 0.11.0, < 1",
21
21
  "requests >= 2.31.0, < 3",
22
- "tftpy >= 0.8.2, < 1",
22
+ "tftpy >= 0.8.2, <= 0.8.5",
23
23
  ]
24
24
  readme = "README.md"
25
25
  keywords = ["tinycontrol", "lk", "tcpdu"]
@@ -0,0 +1 @@
1
+ __version__ = '0.6.0'
@@ -59,7 +59,6 @@ class DeviceModel(ABC):
59
59
  session: Union[ClientSession, None] = None
60
60
 
61
61
  _context: Dict[str, Dict] = field(init=False, default_factory=dict)
62
- _close_session: bool = False
63
62
 
64
63
  def __post_init__(self):
65
64
  if self.schema == "https" and self.port != 443:
@@ -129,7 +128,9 @@ class DeviceModel(ABC):
129
128
  password=self.password,
130
129
  **kwargs,
131
130
  )
132
- return self._get(response.get("parsed"), path, skip_keys, remove_mapped_keys)
131
+ parsed_data = response.get("parsed")
132
+ parsed_data["_ping"] = response.get("ping")
133
+ return self._get(parsed_data, path, skip_keys, remove_mapped_keys)
133
134
 
134
135
  def set_out(self, index, value=None):
135
136
  """Set output state to value or toggle if value is None."""
@@ -215,9 +216,6 @@ class DeviceModel(ABC):
215
216
  **kwargs,
216
217
  ):
217
218
  """Async version of get."""
218
- if self.session is None:
219
- self.session = ClientSession()
220
- self._close_session = True
221
219
  response = await async_get(
222
220
  self.host,
223
221
  path,
@@ -228,7 +226,9 @@ class DeviceModel(ABC):
228
226
  session=self.session,
229
227
  **kwargs,
230
228
  )
231
- return self._get(response.get("parsed"), path, skip_keys, remove_mapped_keys)
229
+ parsed_data = response.get("parsed")
230
+ parsed_data["_ping"] = response.get("ping")
231
+ return self._get(parsed_data, path, skip_keys, remove_mapped_keys)
232
232
 
233
233
  async def async_set_out(self, index, value=None):
234
234
  """Async set_out."""
@@ -265,30 +265,6 @@ class DeviceModel(ABC):
265
265
  f"{self.__class__.__name__} does not support get_all command"
266
266
  )
267
267
 
268
- # region Session handling for asyncio
269
- async def close(self) -> None:
270
- """Close open client session."""
271
- await self.session.close()
272
-
273
- async def __aenter__(self) -> "DeviceModel":
274
- """Async enter.
275
-
276
- Returns
277
- -------
278
- The Device object.
279
- """
280
- return self
281
-
282
- async def __aexit__(self, *_exc_info: object) -> None:
283
- """Async exit.
284
-
285
- Args:
286
- ----
287
- _exc_info: Exec type.
288
- """
289
- await self.close()
290
-
291
- # endregion
292
268
  # endregion
293
269
 
294
270
 
@@ -1,9 +1,21 @@
1
- """Functions to send HTTP requests via requests and aiohttp modules."""
1
+ """Functions to send HTTP requests via requests and aiohttp modules.
2
+
3
+ Ping can be included in response for aiohttp request if the ClientSession
4
+ uses our trace_configs. It can be achieved in two ways:
5
+
6
+ 1. Using `get_session()` to get it with trace_configs set.
7
+ 2. When creating own session adding `get_trace_config()` in constructor params:
8
+ ```py
9
+ session = ClientSession(trace_configs=[get_trace_config()])
10
+ ```
11
+ """
2
12
 
3
13
  import asyncio
4
14
  import json
5
15
  import socket
16
+ import time
6
17
  from functools import wraps
18
+ from types import SimpleNamespace
7
19
  from typing import Any, Dict, Union
8
20
  from xml.etree import ElementTree
9
21
 
@@ -18,6 +30,7 @@ from aiohttp import (
18
30
  ClientTimeout,
19
31
  InvalidURL,
20
32
  ServerDisconnectedError,
33
+ TraceConfig,
21
34
  )
22
35
 
23
36
  from .exceptions import (
@@ -35,6 +48,7 @@ DEFAULT_TIMEOUT = 3
35
48
  DEFAULT_RETRIES = 1
36
49
 
37
50
 
51
+ # region Helper functions for handling requests/responses
38
52
  def _parse_parameters(
39
53
  host, path, schema, port, username, password, timeout, retries, verify
40
54
  ):
@@ -77,7 +91,7 @@ def _handle_errors(exc, response=None):
77
91
  ClientConnectionError,
78
92
  socket.gaierror,
79
93
  ) as exc:
80
- raise TinyToolsRequestConnectionError("Connection error") from exc
94
+ raise TinyToolsRequestConnectionError(f"Connection error ({exc})") from exc
81
95
  except (requests.exceptions.InvalidURL, InvalidURL) as exc:
82
96
  raise TinyToolsRequestError("Invalid URL") from exc
83
97
  except (requests.exceptions.HTTPError, ClientResponseError) as exc:
@@ -103,10 +117,12 @@ def _handle_content(content_type, content, response):
103
117
  "_response": response,
104
118
  "parsed": None,
105
119
  }
106
- # TODO: Add ping for requests method (ATM missing for aiohttp).
107
120
  if hasattr(response, "elapsed"):
108
121
  result["elapsed"] = response.elapsed.total_seconds()
109
- result["ping"] = round(result["elapsed"] * 1000, 3)
122
+ result["ping"] = round(result["elapsed"] * 1000, 3) # in ms
123
+ elif hasattr(response, "elapsed_aio"):
124
+ result["elapsed"] = response.elapsed_aio
125
+ result["ping"] = round(result["elapsed"] * 1000, 3) # in ms
110
126
  try:
111
127
  if content_type == "application/json":
112
128
  result["parsed"] = json.loads(content)
@@ -119,8 +135,10 @@ def _handle_content(content_type, content, response):
119
135
  except (ElementTree.ParseError, ValueError) as exc:
120
136
  raise TinyToolsRequestError("Cannot parse response") from exc
121
137
  return result
138
+ # endregion
122
139
 
123
140
 
141
+ # region Synchronous requests
124
142
  def request(
125
143
  method: str,
126
144
  host: str,
@@ -156,7 +174,7 @@ def request(
156
174
  verify=verify,
157
175
  )
158
176
  response.raise_for_status()
159
- except requests.exceptions.ConnectTimeout as exc:
177
+ except (requests.exceptions.ConnectTimeout, requests.exceptions.ReadTimeout) as exc:
160
178
  if retries:
161
179
  return request(
162
180
  method,
@@ -209,6 +227,33 @@ def post(*args, **kwargs):
209
227
  headers={"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"},
210
228
  **kwargs,
211
229
  )
230
+ # endregion
231
+
232
+
233
+ # region Asynchronous requests (aiohttp based)
234
+ async def on_request_start(session, trace_config_ctx, params):
235
+ """Set timestamp for timing tracking."""
236
+ trace_config_ctx.start = time.perf_counter()
237
+
238
+
239
+ async def on_request_end(session, trace_config_ctx, params):
240
+ """Calculate timing and set in the request context."""
241
+ elapsed = time.perf_counter() - trace_config_ctx.start
242
+ if hasattr(trace_config_ctx, "trace_request_ctx"):
243
+ trace_config_ctx.trace_request_ctx.elapsed = elapsed
244
+
245
+
246
+ def get_trace_config() -> TraceConfig:
247
+ """Return TraceConfig with signal handlers to track request timing."""
248
+ trace_config = TraceConfig()
249
+ trace_config.on_request_start.append(on_request_start)
250
+ trace_config.on_request_end.append(on_request_end)
251
+ return trace_config
252
+
253
+
254
+ def get_session() -> ClientSession:
255
+ """Return ClientSession with trace_config for timing tracking."""
256
+ return ClientSession(trace_configs=[get_trace_config()])
212
257
 
213
258
 
214
259
  async def async_request(
@@ -238,6 +283,7 @@ async def async_request(
238
283
  auth = BasicAuth(*auth)
239
284
  response = None
240
285
  try:
286
+ timing = SimpleNamespace(elapsed=None)
241
287
  async with session.request(
242
288
  method,
243
289
  url,
@@ -245,9 +291,14 @@ async def async_request(
245
291
  params=params,
246
292
  timeout=ClientTimeout(total=timeout),
247
293
  verify_ssl=verify,
294
+ trace_request_ctx=timing,
248
295
  ) as response:
249
296
  response.raise_for_status()
250
297
  content = await response.text()
298
+ # Try to handle timing
299
+ elapsed = timing.elapsed
300
+ if elapsed is not None:
301
+ response.elapsed_aio = elapsed
251
302
  except (asyncio.TimeoutError, ClientOSError, ServerDisconnectedError) as exc:
252
303
  # Retry only on timeout and few specific client connection errors
253
304
  # (they happen randomly at least on Windows).
@@ -287,3 +338,4 @@ async def async_get(*args, silent=False, **kwargs):
287
338
  if silent:
288
339
  return None
289
340
  raise
341
+ # endregion
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tinytoolslib
3
- Version: 0.5.0
4
- Summary: Set of tools for use with Tinycontrol devices like LK2.X, LK3.X, LK4.X or tcPDU.
3
+ Version: 0.6.0
4
+ Summary: Set of tools for use with tinycontrol devices like LK2.X, LK3.X, LK4.X or tcPDU.
5
5
  Author-email: Bartek Barszczewski <tinycontrol.software@gmail.com>
6
6
  License-Expression: Apache-2.0
7
7
  Keywords: tinycontrol,lk,tcpdu
@@ -12,7 +12,7 @@ Description-Content-Type: text/markdown
12
12
  Requires-Dist: aiohttp<4,>=3.9.3
13
13
  Requires-Dist: netifaces<1,>=0.11.0
14
14
  Requires-Dist: requests<3,>=2.31.0
15
- Requires-Dist: tftpy<1,>=0.8.2
15
+ Requires-Dist: tftpy<=0.8.5,>=0.8.2
16
16
 
17
17
  # tinyToolsLib
18
18
 
@@ -1,4 +1,4 @@
1
1
  aiohttp<4,>=3.9.3
2
2
  netifaces<1,>=0.11.0
3
3
  requests<3,>=2.31.0
4
- tftpy<1,>=0.8.2
4
+ tftpy<=0.8.5,>=0.8.2
@@ -1 +0,0 @@
1
- __version__ = '0.5.0'
File without changes
File without changes