python-bsblan 0.5.19__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,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-bsblan
3
- Version: 0.5.19
3
+ Version: 0.6.0
4
4
  Summary: Asynchronous Python client for BSBLAN
5
5
  Home-page: https://github.com/liudger/python-bsblan
6
6
  License: MIT
@@ -9,16 +9,15 @@ Author: Willem-Jan van Rootselaar
9
9
  Author-email: liudgervr@gmail.com
10
10
  Maintainer: Willem-Jan van Rootselaar
11
11
  Maintainer-email: liudgervr@gmail.com
12
- Requires-Python: >=3.10,<4.0
12
+ Requires-Python: >=3.12,<4.0
13
13
  Classifier: Development Status :: 3 - Alpha
14
14
  Classifier: Framework :: AsyncIO
15
15
  Classifier: Intended Audience :: Developers
16
16
  Classifier: License :: OSI Approved :: MIT License
17
17
  Classifier: Natural Language :: English
18
18
  Classifier: Programming Language :: Python :: 3
19
- Classifier: Programming Language :: Python :: 3.10
20
- Classifier: Programming Language :: Python :: 3.11
21
19
  Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.11
22
21
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
22
  Requires-Dist: aiohttp (>=3.8.1)
24
23
  Requires-Dist: async-timeout (>=4.0.3,<5.0.0)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "python-bsblan"
3
- version = "0.5.19"
3
+ version = "0.6.00"
4
4
  description = "Asynchronous Python client for BSBLAN"
5
5
  authors = ["Willem-Jan van Rootselaar <liudgervr@gmail.com>"]
6
6
  maintainers = ["Willem-Jan van Rootselaar <liudgervr@gmail.com>"]
@@ -15,8 +15,8 @@ classifiers = [
15
15
  "Framework :: AsyncIO",
16
16
  "Intended Audience :: Developers",
17
17
  "Natural Language :: English",
18
- "Programming Language :: Python :: 3.10",
19
18
  "Programming Language :: Python :: 3.11",
19
+ "Programming Language :: Python :: 3.12",
20
20
  "Programming Language :: Python :: 3",
21
21
  "Topic :: Software Development :: Libraries :: Python Modules",
22
22
  ]
@@ -25,7 +25,7 @@ packages = [
25
25
  ]
26
26
 
27
27
  [tool.poetry.dependencies]
28
- python = "^3.10"
28
+ python = "^3.12"
29
29
  aiohttp = ">=3.8.1"
30
30
  yarl = ">=1.7.2"
31
31
  packaging = ">=21.3"
@@ -57,7 +57,6 @@ darglint = "^1.8.1"
57
57
  safety = "^3.0.0"
58
58
  codespell = "^2.2.2"
59
59
  bandit = "^1.7.4"
60
- pytest-mock = "^3.10.0"
61
60
 
62
61
  [tool.poetry.urls]
63
62
  "Bug Tracker" = "https://github.com/liudger/python-bsblan/issues"
@@ -80,7 +79,6 @@ multi_line_output = 3
80
79
  # free to run mypy on Windows, Linux, or macOS and get consistent
81
80
  # results.
82
81
  platform = "linux"
83
- python_version = "3.10"
84
82
 
85
83
  # show error messages from unrelated files
86
84
  follow_imports = "normal"
@@ -1,11 +1,12 @@
1
1
  """Asynchronous Python client for BSBLAN."""
2
2
 
3
- from .bsblan import BSBLAN
3
+ from .bsblan import BSBLAN, BSBLANConfig
4
4
  from .exceptions import BSBLANConnectionError, BSBLANError
5
5
  from .models import Device, Info, Sensor, State, StaticState
6
6
 
7
7
  __all__ = [
8
8
  "BSBLAN",
9
+ "BSBLANConfig",
9
10
  "BSBLANConnectionError",
10
11
  "BSBLANError",
11
12
  "Info",
@@ -1,19 +1,17 @@
1
1
  """Asynchronous Python client for BSB-Lan."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  import asyncio
5
6
  import logging
6
- import socket
7
7
  from asyncio.log import logger
8
8
  from dataclasses import dataclass, field
9
9
  from importlib import metadata
10
- from typing import Any, TypedDict, cast
10
+ from typing import TYPE_CHECKING, Any, Mapping, TypedDict, cast
11
11
 
12
- import async_timeout
13
- import backoff
14
- from aiohttp.client import ClientError, ClientResponseError, ClientSession
12
+ import aiohttp
13
+ from aiohttp.client import ClientSession
15
14
  from aiohttp.hdrs import METH_POST
16
- from aiohttp.helpers import BasicAuth
17
15
  from packaging import version as pkg_version
18
16
  from typing_extensions import Self
19
17
  from yarl import URL
@@ -41,12 +39,15 @@ from .exceptions import (
41
39
  )
42
40
  from .models import Device, Info, Sensor, State, StaticState
43
41
 
42
+ if TYPE_CHECKING:
43
+ from aiohttp.helpers import BasicAuth
44
+
44
45
  logging.basicConfig(level=logging.DEBUG)
45
46
 
46
47
 
47
48
  @dataclass
48
- class BSBLAN:
49
- """Main class for handling connections with BSBLAN."""
49
+ class BSBLANConfig:
50
+ """Configuration for BSBLAN."""
50
51
 
51
52
  host: str
52
53
  username: str | None = None
@@ -54,7 +55,12 @@ class BSBLAN:
54
55
  passkey: str | None = None
55
56
  port: int = 80
56
57
  request_timeout: int = 10
57
- session: ClientSession | None = None
58
+
59
+
60
+ @dataclass
61
+ class BSBLAN:
62
+ """Main class for handling connections with BSBLAN."""
63
+
58
64
  _version: str = ""
59
65
  _heating_params: list[str] | None = None
60
66
  _string_circuit1: str | None = None
@@ -69,14 +75,49 @@ class BSBLAN:
69
75
  _auth: BasicAuth | None = None
70
76
  _close_session: bool = False
71
77
 
78
+ def __init__(self, config: BSBLANConfig) -> None:
79
+ """Initialize the BSBLAN object.
80
+
81
+ Args:
82
+ ----
83
+ config: Configuration for the BSBLAN object.
84
+
85
+ """
86
+ self.config = config
87
+ self.session: ClientSession | None = None
88
+ self._close_session = False
89
+
90
+ async def __aenter__(self) -> Self:
91
+ """Enter method for the context manager.
92
+
93
+ Returns
94
+ -------
95
+ Self: The current instance of the context manager.
96
+
97
+ """
98
+ if self.session is None:
99
+ self.session = aiohttp.ClientSession()
100
+ self._close_session = True
101
+ return self
102
+
103
+ async def __aexit__(self, *args: object) -> None:
104
+ """Exit method for the context manager.
105
+
106
+ Args:
107
+ ----
108
+ *args: Arguments passed to the exit method.
109
+
110
+ """
111
+ if self._close_session and self.session:
112
+ await self.session.close()
113
+
72
114
  # cSpell:ignore BSBLAN
73
- @backoff.on_exception(backoff.expo, BSBLANConnectionError, max_tries=3, logger=None)
74
115
  async def _request(
75
116
  self,
76
117
  method: str = METH_POST,
77
118
  base_path: str = "/JQ",
78
119
  data: dict[str, object] | None = None,
79
- params: dict[str, object] | None = None,
120
+ params: Mapping[str, str | int] | str | None = None,
80
121
  ) -> dict[str, Any]:
81
122
  """Handle a request to a BSBLAN device.
82
123
 
@@ -110,18 +151,19 @@ class BSBLAN:
110
151
  version = "0.0.0"
111
152
 
112
153
  # retrieve passkey for custom url
113
- if self.passkey is not None:
114
- base_path = f"/{self.passkey}{base_path}"
154
+ if self.config.passkey:
155
+ base_path = f"/{self.config.passkey}{base_path}"
115
156
 
116
157
  url = URL.build(
117
158
  scheme="http",
118
- host=self.host,
119
- port=self.port,
159
+ host=self.config.host,
160
+ port=self.config.port,
120
161
  path=base_path,
121
- ).join(URL())
162
+ )
122
163
 
123
- if self._auth is None and self.username and self.password:
124
- self._auth = BasicAuth(self.username, self.password)
164
+ auth = None
165
+ if self.config.username and self.config.password:
166
+ auth = aiohttp.BasicAuth(self.config.username, self.config.password)
125
167
 
126
168
  headers = {
127
169
  "User-Agent": f"PythonBSBLAN/{version}",
@@ -133,31 +175,23 @@ class BSBLAN:
133
175
  self._close_session = True
134
176
 
135
177
  try:
136
- async with async_timeout.timeout(self.request_timeout):
137
- response = await self.session.request(
178
+ async with asyncio.timeout(self.config.request_timeout):
179
+ async with self.session.request(
138
180
  method,
139
181
  url,
140
- auth=self._auth,
182
+ auth=auth,
141
183
  params=params,
142
184
  json=data,
143
185
  headers=headers,
144
- )
145
- response.raise_for_status()
146
- except asyncio.TimeoutError as exception:
147
- raise BSBLANConnectionError(BSBLANConnectionError.message) from exception
148
- except (
149
- ClientError,
150
- ClientResponseError,
151
- socket.gaierror,
152
- ) as exception:
153
- raise BSBLANConnectionError(BSBLANConnectionError.message) from exception
154
-
155
- content_type = response.headers.get("Content-Type", "")
156
- if "application/json" not in content_type:
157
- text = await response.text()
158
- raise BSBLANError(BSBLANError.message + f" ({text})")
159
-
160
- return cast(dict[str, Any], await response.json())
186
+ ) as response:
187
+ response.raise_for_status()
188
+ return cast(dict[str, Any], await response.json())
189
+ except asyncio.TimeoutError as e:
190
+ raise BSBLANConnectionError(BSBLANConnectionError.message_timeout) from e
191
+ except aiohttp.ClientError as e:
192
+ raise BSBLANConnectionError(BSBLANConnectionError.message_error) from e
193
+ except ValueError as e:
194
+ raise BSBLANError(str(e)) from e
161
195
 
162
196
  async def state(self) -> State:
163
197
  """Get the current state from BSBLAN device.
@@ -361,29 +395,3 @@ class BSBLAN:
361
395
  # Now it only checks if it could set value.
362
396
  response = await self._request(base_path="/JS", data=dict(state))
363
397
  logger.debug("response for setting: %s", response)
364
-
365
- async def close(self) -> None:
366
- """Close open client session."""
367
- if self.session and self._close_session:
368
- await self.session.close()
369
-
370
- async def __aenter__(self) -> Self:
371
- """Async enter.
372
-
373
- Returns
374
- -------
375
- The BSBLAN object.
376
-
377
- """
378
- logger.debug("BSBLAN: %s", self)
379
- return self
380
-
381
- async def __aexit__(self, *_exc_info: object) -> None:
382
- """Async exit.
383
-
384
- Args:
385
- ----
386
- *_exc_info: Exec type.
387
-
388
- """
389
- await self.close()
@@ -1,4 +1,5 @@
1
1
  """Exceptions for for BSB-Lan."""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
 
@@ -33,7 +34,8 @@ class BSBLANConnectionError(BSBLANError):
33
34
 
34
35
  """
35
36
 
36
- message = "Error occurred while connecting to BSBLAN device."
37
+ message_timeout = "Timeout occurred while connecting to BSBLAN device."
38
+ message_error = "Error occurred while connecting to BSBLAN device."
37
39
 
38
40
  def __init__(self, response: str | None = None) -> None:
39
41
  """Initialize a new instance of the BSBLANConnectionError class.
@@ -1,4 +1,5 @@
1
1
  """Models for BSB-Lan."""
2
+
2
3
  from dataclasses import dataclass, field
3
4
 
4
5
  from mashumaro.mixins.json import DataClassJSONMixin
File without changes