pybluecurrent 0.0.3__tar.gz → 0.1.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 (24) hide show
  1. {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/.pre-commit-config.yaml +2 -2
  2. {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/PKG-INFO +33 -48
  3. {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/README.md +26 -42
  4. {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/pyproject.toml +9 -4
  5. pybluecurrent-0.1.0/src/pybluecurrent/_version.py +34 -0
  6. {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/src/pybluecurrent/client.py +40 -40
  7. {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/src/pybluecurrent.egg-info/PKG-INFO +33 -48
  8. {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/src/pybluecurrent.egg-info/requires.txt +4 -4
  9. {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/tests/conftest.py +4 -19
  10. {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/tests/test_client.py +25 -42
  11. pybluecurrent-0.0.3/src/pybluecurrent/_version.py +0 -16
  12. {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/.github/workflows/publish.yaml +0 -0
  13. {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/.github/workflows/test.yaml +0 -0
  14. {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/.gitignore +0 -0
  15. {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/LICENSE +0 -0
  16. {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/setup.cfg +0 -0
  17. {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/src/pybluecurrent/__init__.py +0 -0
  18. {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/src/pybluecurrent/exceptions.py +0 -0
  19. {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/src/pybluecurrent/py.typed +0 -0
  20. {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/src/pybluecurrent/utilities.py +0 -0
  21. {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/src/pybluecurrent.egg-info/SOURCES.txt +0 -0
  22. {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/src/pybluecurrent.egg-info/dependency_links.txt +0 -0
  23. {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/src/pybluecurrent.egg-info/top_level.txt +0 -0
  24. {pybluecurrent-0.0.3 → pybluecurrent-0.1.0}/tests/test_utilities.py +0 -0
@@ -1,7 +1,7 @@
1
1
  fail_fast: true
2
2
  repos:
3
3
  - repo: https://github.com/pre-commit/pre-commit-hooks
4
- rev: v3.2.0
4
+ rev: v6.0.0
5
5
  hooks:
6
6
  - id: trailing-whitespace
7
7
  exclude: '\.(md|markdown)$'
@@ -27,7 +27,7 @@ repos:
27
27
  - id: isort
28
28
  exclude: __init__.py
29
29
  - repo: https://github.com/pre-commit/mirrors-mypy
30
- rev: 6e63c9e
30
+ rev: v1.18.2
31
31
  hooks:
32
32
  - id: mypy
33
33
  additional_dependencies:
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: pybluecurrent
3
- Version: 0.0.3
3
+ Version: 0.1.0
4
4
  Summary: Python client for BlueCurrent charge points.
5
5
  Author-email: Rogier van der Geer <rogier@vander-geer.nl>
6
6
  License: MIT
@@ -15,14 +15,15 @@ Requires-Python: >=3.10
15
15
  Description-Content-Type: text/markdown
16
16
  License-File: LICENSE
17
17
  Requires-Dist: asyncio-multisubscriber-queue>=0.4.1
18
- Requires-Dist: requests>=2.31.0
18
+ Requires-Dist: httpx>=0.28
19
19
  Requires-Dist: sjcl>=0.2.1
20
- Requires-Dist: websockets>=11.0.3
20
+ Requires-Dist: websockets>=14.0
21
21
  Provides-Extra: dev
22
22
  Requires-Dist: black==23.3.0; extra == "dev"
23
23
  Requires-Dist: pre-commit>=3.3.3; extra == "dev"
24
- Requires-Dist: pytest==8.1.2; extra == "dev"
25
- Requires-Dist: pytest-asyncio==0.21.1; extra == "dev"
24
+ Requires-Dist: pytest==8.4.2; extra == "dev"
25
+ Requires-Dist: pytest-asyncio==1.2.0; extra == "dev"
26
+ Dynamic: license-file
26
27
 
27
28
  # pybluecurrent
28
29
 
@@ -42,36 +43,32 @@ from pybluecurrent import BlueCurrentClient
42
43
  client = BlueCurrentClient("your_username", "your_secret_password")
43
44
 
44
45
  async with client:
45
- charge_points = await client.get_charge_points()
46
-
47
- transactions = client.get_transactions(charge_points[0]["evse_id"])
46
+ charge_points = await client.get_charge_points()
47
+ transactions = await client.get_transactions(charge_points[0]["evse_id"])
48
48
  ```
49
49
 
50
50
  ## Methods
51
51
 
52
- BlueCurrent exposes two APIs, a synchronous REST API as well as an asynchronous Websocket API.
53
- As a result, the `BlueCurrentClient` also exposes synchronous as well as asynchronous methods:
52
+ The `BlueCurrentClient` exposes the following methods:
54
53
 
55
- - [Asynchronous](#asynchronous)
56
- - [`get_account`](#getaccount---get-your-account-information)
57
- - [`get_charge_cards`](#getchargecards---get-your-charge-cards)
58
- - [`get_charge_points`](#getchargepoints---get-your-charge-points)
59
- - [`get_charge_point_settings`](#getchargepointsettings---get-the-settings-of-a-charge-point)
60
- - [`get_grid_status`](#getgridstatus---get-the-grid-status-associated-to-a-charge-point)
61
- - [`get_sustainability_status`](#getsustainabilitystatus---get-statistics-on-the-sustainability-of-all-your-charge-points)
62
- - [`set_plug_and_charge_charge_card`](#setplugandchargechargecard---set-the-charge-card-for-plug-and-charge)
63
- - [`set_status`](#setstatus---enable-or-disable-a-charge-point)
64
- - [Synchronous](#synchronous)
65
- - [`login`](#login---log-in)
66
- - [`get_charge_point_status`](#getchargepointstatus---get-the-status-of-a-charge-point)
67
- - [`get_contracts`](#getcontracts---get-your-contracts)
68
- - [`get_grids`](#getgrids---get-your-grid-connections)
69
- - [`get_transactions`](#gettransactions---get-a-list-of-transactions)
70
- - [`iterate_transactions`](#iteratetransactions---iterate-through-your-transactions)
71
-
72
- ### Asynchronous
73
-
74
- The async methods can only be used when the websocket client is connected. For example:
54
+ - [`get_account`](#getaccount---get-your-account-information)
55
+ - [`get_charge_cards`](#getchargecards---get-your-charge-cards)
56
+ - [`get_charge_points`](#getchargepoints---get-your-charge-points)
57
+ - [`get_charge_point_settings`](#getchargepointsettings---get-the-settings-of-a-charge-point)
58
+ - [`get_grid_status`](#getgridstatus---get-the-grid-status-associated-to-a-charge-point)
59
+ - [`get_sustainability_status`](#getsustainabilitystatus---get-statistics-on-the-sustainability-of-all-your-charge-points)
60
+ - [`set_plug_and_charge_charge_card`](#setplugandchargechargecard---set-the-charge-card-for-plug-and-charge)
61
+ - [`set_status`](#setstatus---enable-or-disable-a-charge-point)
62
+ - [`login`](#login---log-in)
63
+ - [`get_charge_point_status`](#getchargepointstatus---get-the-status-of-a-charge-point)
64
+ - [`get_contracts`](#getcontracts---get-your-contracts)
65
+ - [`get_grids`](#getgrids---get-your-grid-connections)
66
+ - [`get_transactions`](#gettransactions---get-a-list-of-transactions)
67
+ - [`iterate_transactions`](#iteratetransactions---iterate-through-your-transactions)
68
+
69
+ ### Connection
70
+
71
+ The client can only be used when the websocket client is connected. For example:
75
72
  ```python
76
73
  client = BlueCurrentClient("your_username", "your_secret_password")
77
74
  async with client:
@@ -247,22 +244,10 @@ async def set_status(self, evse_id: str, enabled: bool) -> None
247
244
  - `evse_id`: The ID of the charge point.
248
245
  - `enabled`: Boolean that indicates the desired status.
249
246
 
250
- ### Synchronous
251
-
252
- #### `login` - Log in
253
- ```python
254
- def login(self) -> None
255
- ```
256
-
257
- This method does not do anything if the client is already logged in.
258
- Connection to the websocket api (async with client) automatically logs in the client,
259
- so this endpoint is not needed when using the async API.
260
-
261
-
262
247
  #### `get_charge_point_status` - Get the status of a charge point.
263
248
 
264
249
  ```python
265
- def get_charge_point_status(self, evse_id: str) -> dict[str, datetime | float | int | str | None]
250
+ async def get_charge_point_status(self, evse_id: str) -> dict[str, datetime | float | int | str | None]
266
251
  ```
267
252
 
268
253
  ##### Arguments
@@ -295,7 +280,7 @@ A dictionary with the chargepoint status:
295
280
  #### `get_contracts` - Get your contracts.
296
281
 
297
282
  ```python
298
- def get_contracts(self) -> list[dict[str, str]]
283
+ async def get_contracts(self) -> list[dict[str, str]]
299
284
  ```
300
285
 
301
286
  ##### Returns
@@ -315,7 +300,7 @@ A list of dictionaries, each representing a contract:
315
300
  #### `get_grids` - Get your grid connections.
316
301
 
317
302
  ```python
318
- def get_grids(self) -> list[dict[str, bool | dict[str, str] | str]]
303
+ async def get_grids(self) -> list[dict[str, bool | dict[str, str] | str]]
319
304
  ```
320
305
 
321
306
  ##### Returns
@@ -334,7 +319,7 @@ A list of dictionaries, each representing a grid:
334
319
  #### `get_transactions` - Get a list of transactions.
335
320
 
336
321
  ```python
337
- def get_transactions(
322
+ async def get_transactions(
338
323
  self, evse_id: str, newest_first: bool = True, page: int = 1
339
324
  ) -> dict[str, int | list[dict[str, Any]]]
340
325
  ```
@@ -376,7 +361,7 @@ A dictionary like this:
376
361
  #### `iterate_transactions` - Iterate through your transactions
377
362
 
378
363
  ```python
379
- def iterate_transactions(self, evse_id: str, newest_first: bool = True) -> Iterable[dict[str, Any]]
364
+ async def iterate_transactions(self, evse_id: str, newest_first: bool = True) -> AsyncIterable[dict[str, Any]]
380
365
  ```
381
366
 
382
367
  ##### Arguments
@@ -16,36 +16,32 @@ from pybluecurrent import BlueCurrentClient
16
16
  client = BlueCurrentClient("your_username", "your_secret_password")
17
17
 
18
18
  async with client:
19
- charge_points = await client.get_charge_points()
20
-
21
- transactions = client.get_transactions(charge_points[0]["evse_id"])
19
+ charge_points = await client.get_charge_points()
20
+ transactions = await client.get_transactions(charge_points[0]["evse_id"])
22
21
  ```
23
22
 
24
23
  ## Methods
25
24
 
26
- BlueCurrent exposes two APIs, a synchronous REST API as well as an asynchronous Websocket API.
27
- As a result, the `BlueCurrentClient` also exposes synchronous as well as asynchronous methods:
25
+ The `BlueCurrentClient` exposes the following methods:
28
26
 
29
- - [Asynchronous](#asynchronous)
30
- - [`get_account`](#getaccount---get-your-account-information)
31
- - [`get_charge_cards`](#getchargecards---get-your-charge-cards)
32
- - [`get_charge_points`](#getchargepoints---get-your-charge-points)
33
- - [`get_charge_point_settings`](#getchargepointsettings---get-the-settings-of-a-charge-point)
34
- - [`get_grid_status`](#getgridstatus---get-the-grid-status-associated-to-a-charge-point)
35
- - [`get_sustainability_status`](#getsustainabilitystatus---get-statistics-on-the-sustainability-of-all-your-charge-points)
36
- - [`set_plug_and_charge_charge_card`](#setplugandchargechargecard---set-the-charge-card-for-plug-and-charge)
37
- - [`set_status`](#setstatus---enable-or-disable-a-charge-point)
38
- - [Synchronous](#synchronous)
39
- - [`login`](#login---log-in)
40
- - [`get_charge_point_status`](#getchargepointstatus---get-the-status-of-a-charge-point)
41
- - [`get_contracts`](#getcontracts---get-your-contracts)
42
- - [`get_grids`](#getgrids---get-your-grid-connections)
43
- - [`get_transactions`](#gettransactions---get-a-list-of-transactions)
44
- - [`iterate_transactions`](#iteratetransactions---iterate-through-your-transactions)
45
-
46
- ### Asynchronous
47
-
48
- The async methods can only be used when the websocket client is connected. For example:
27
+ - [`get_account`](#getaccount---get-your-account-information)
28
+ - [`get_charge_cards`](#getchargecards---get-your-charge-cards)
29
+ - [`get_charge_points`](#getchargepoints---get-your-charge-points)
30
+ - [`get_charge_point_settings`](#getchargepointsettings---get-the-settings-of-a-charge-point)
31
+ - [`get_grid_status`](#getgridstatus---get-the-grid-status-associated-to-a-charge-point)
32
+ - [`get_sustainability_status`](#getsustainabilitystatus---get-statistics-on-the-sustainability-of-all-your-charge-points)
33
+ - [`set_plug_and_charge_charge_card`](#setplugandchargechargecard---set-the-charge-card-for-plug-and-charge)
34
+ - [`set_status`](#setstatus---enable-or-disable-a-charge-point)
35
+ - [`login`](#login---log-in)
36
+ - [`get_charge_point_status`](#getchargepointstatus---get-the-status-of-a-charge-point)
37
+ - [`get_contracts`](#getcontracts---get-your-contracts)
38
+ - [`get_grids`](#getgrids---get-your-grid-connections)
39
+ - [`get_transactions`](#gettransactions---get-a-list-of-transactions)
40
+ - [`iterate_transactions`](#iteratetransactions---iterate-through-your-transactions)
41
+
42
+ ### Connection
43
+
44
+ The client can only be used when the websocket client is connected. For example:
49
45
  ```python
50
46
  client = BlueCurrentClient("your_username", "your_secret_password")
51
47
  async with client:
@@ -221,22 +217,10 @@ async def set_status(self, evse_id: str, enabled: bool) -> None
221
217
  - `evse_id`: The ID of the charge point.
222
218
  - `enabled`: Boolean that indicates the desired status.
223
219
 
224
- ### Synchronous
225
-
226
- #### `login` - Log in
227
- ```python
228
- def login(self) -> None
229
- ```
230
-
231
- This method does not do anything if the client is already logged in.
232
- Connection to the websocket api (async with client) automatically logs in the client,
233
- so this endpoint is not needed when using the async API.
234
-
235
-
236
220
  #### `get_charge_point_status` - Get the status of a charge point.
237
221
 
238
222
  ```python
239
- def get_charge_point_status(self, evse_id: str) -> dict[str, datetime | float | int | str | None]
223
+ async def get_charge_point_status(self, evse_id: str) -> dict[str, datetime | float | int | str | None]
240
224
  ```
241
225
 
242
226
  ##### Arguments
@@ -269,7 +253,7 @@ A dictionary with the chargepoint status:
269
253
  #### `get_contracts` - Get your contracts.
270
254
 
271
255
  ```python
272
- def get_contracts(self) -> list[dict[str, str]]
256
+ async def get_contracts(self) -> list[dict[str, str]]
273
257
  ```
274
258
 
275
259
  ##### Returns
@@ -289,7 +273,7 @@ A list of dictionaries, each representing a contract:
289
273
  #### `get_grids` - Get your grid connections.
290
274
 
291
275
  ```python
292
- def get_grids(self) -> list[dict[str, bool | dict[str, str] | str]]
276
+ async def get_grids(self) -> list[dict[str, bool | dict[str, str] | str]]
293
277
  ```
294
278
 
295
279
  ##### Returns
@@ -308,7 +292,7 @@ A list of dictionaries, each representing a grid:
308
292
  #### `get_transactions` - Get a list of transactions.
309
293
 
310
294
  ```python
311
- def get_transactions(
295
+ async def get_transactions(
312
296
  self, evse_id: str, newest_first: bool = True, page: int = 1
313
297
  ) -> dict[str, int | list[dict[str, Any]]]
314
298
  ```
@@ -350,7 +334,7 @@ A dictionary like this:
350
334
  #### `iterate_transactions` - Iterate through your transactions
351
335
 
352
336
  ```python
353
- def iterate_transactions(self, evse_id: str, newest_first: bool = True) -> Iterable[dict[str, Any]]
337
+ async def iterate_transactions(self, evse_id: str, newest_first: bool = True) -> AsyncIterable[dict[str, Any]]
354
338
  ```
355
339
 
356
340
  ##### Arguments
@@ -21,18 +21,18 @@ classifiers = [
21
21
  "License :: OSI Approved :: MIT License",
22
22
  "Operating System :: OS Independent",
23
23
  "Programming Language :: Python :: 3",
24
- "Typing :: Typed"
24
+ "Typing :: Typed",
25
25
  ]
26
26
  dependencies = [
27
27
  "asyncio-multisubscriber-queue>=0.4.1",
28
- "requests>=2.31.0",
28
+ "httpx>=0.28",
29
29
  "sjcl>=0.2.1",
30
- "websockets>=11.0.3",
30
+ "websockets>=14.0",
31
31
  ]
32
32
  dynamic = ["version"]
33
33
 
34
34
  [project.optional-dependencies]
35
- dev = ["black==23.3.0", "pre-commit>=3.3.3", "pytest==8.1.2", "pytest-asyncio==0.21.1"]
35
+ dev = ["black==23.3.0", "pre-commit>=3.3.3", "pytest==8.4.2", "pytest-asyncio==1.2.0"]
36
36
 
37
37
  [project.urls]
38
38
  Repository = "https://github.com/rogiervandergeer/pybluecurrent"
@@ -53,3 +53,8 @@ profile = "black"
53
53
  [tool.ruff]
54
54
  line-length = 120
55
55
  target-version = "py310"
56
+
57
+ [tool.pytest.ini_options]
58
+ asyncio_mode = "auto"
59
+ asyncio_default_fixture_loop_scope = "session"
60
+ asyncio_default_test_loop_scope = "session"
@@ -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'
32
+ __version_tuple__ = version_tuple = (0, 1, 0)
33
+
34
+ __commit_id__ = commit_id = 'g1fb6788fb'
@@ -1,14 +1,14 @@
1
- from asyncio import Task, create_task, run, wait_for
1
+ from asyncio import Task, create_task, wait_for
2
2
  from datetime import date, datetime
3
3
  from json import dumps, loads
4
4
  from logging import getLogger
5
- from typing import Any, Iterable
5
+ from typing import Any, AsyncIterable
6
6
  from uuid import uuid4
7
7
 
8
8
  from asyncio_multisubscriber_queue import MultisubscriberQueue
9
- from requests import get, post
9
+ from httpx import AsyncClient
10
10
  from sjcl import SJCL
11
- from websockets import WebSocketClientProtocol, connect
11
+ from websockets.asyncio.client import ClientConnection, connect
12
12
 
13
13
  from pybluecurrent._version import __version__
14
14
  from pybluecurrent.exceptions import AuthenticationFailed, BlueCurrentException
@@ -24,17 +24,18 @@ class BlueCurrentClient:
24
24
  self.consumer: Task | None = None
25
25
  self.credentials: tuple[str, str] = (username, password)
26
26
  self.logger = getLogger("BlueCurrentClient")
27
+ self.httpx_client: AsyncClient | None = None
27
28
  self.queue = MultisubscriberQueue()
28
- self.socket: WebSocketClientProtocol | None = None
29
+ self.socket: ClientConnection | None = None
29
30
  self.token: str | None = None
30
31
 
31
- ## Asynchronous API ##
32
-
33
32
  async def __aenter__(self) -> "BlueCurrentClient":
34
33
  self.logger.debug("Creating BlueCurrent websocket connection")
35
34
  self.connection = connect(self.socket_url, user_agent_header=self._user_agent)
36
35
  self.socket = await self.connection.__aenter__()
37
36
  self.consumer = create_task(self._handler())
37
+ self.httpx_client = AsyncClient()
38
+ await self.httpx_client.__aenter__()
38
39
  if self.token is None:
39
40
  await self._login()
40
41
  await self._hello()
@@ -44,7 +45,8 @@ class BlueCurrentClient:
44
45
  self.logger.debug("Closing BlueCurrent connection")
45
46
  self.consumer.cancel()
46
47
  await self.connection.__aexit__(exc_type, exc_val, exc_tb)
47
- self.consumer, self.socket = None, None
48
+ await self.httpx_client.__aexit__(exc_type, exc_val, exc_tb)
49
+ self.consumer, self.socket, self.httpx_client = None, None, None
48
50
 
49
51
  async def get_account(self) -> dict[str, bool | date | str]:
50
52
  """
@@ -212,10 +214,17 @@ class BlueCurrentClient:
212
214
 
213
215
  Args:
214
216
  evse_id: A charge point ID.
215
- uid: A charge card UID or None (for no charge card). Defaults to None.
217
+ uid: A charge card UID or None. Defaults to None.
218
+ Setting the plug-and-charge card to None will result in plug-and-charge
219
+ transactions being started without a charge card. Note that the
220
+ charge point status will show "BCU_HOME_USE" as the charge card.
221
+ Setting the plug-and-charge card to "BCU_HOME_USE" has the same effect
222
+ as setting it to None.
216
223
  """
224
+ token_uid = "BCU-APP" if uid is None or uid == "BCU_HOME_USE" else uid
217
225
  await self._send(
218
- dict(command="SET_PLUG_AND_CHARGE_CHARGE_CARD", evse_id=evse_id, token_uid=uid or "BCU-APP"), token=True
226
+ dict(command="SET_PLUG_AND_CHARGE_CHARGE_CARD", evse_id=evse_id, token_uid=token_uid),
227
+ token=True,
219
228
  )
220
229
  result = await self._receive("STATUS_SET_PLUG_AND_CHARGE_CHARGE_CARD")
221
230
  if not result.get("success"):
@@ -256,25 +265,7 @@ class BlueCurrentClient:
256
265
  await self._receive("RECEIVED_SOFT_RESET")
257
266
  return await self._receive("STATUS_SOFT_RESET", timeout=30)
258
267
 
259
- ## Synchronous API ##
260
-
261
- def login(self) -> None:
262
- """
263
- Retrieves a connection token.
264
-
265
- This method does not do anything if the client is already logged in.
266
- Connection to the websocket api (async with client) automatically logs in the client,
267
- so this endpoint is not needed when using the async API.
268
- """
269
-
270
- async def _login(client: BlueCurrentClient) -> None:
271
- async with client:
272
- pass
273
-
274
- if self.token is None:
275
- run(_login(self))
276
-
277
- def get_charge_point_status(self, evse_id: str) -> dict[str, datetime | float | int | str | None]:
268
+ async def get_charge_point_status(self, evse_id: str) -> dict[str, datetime | float | int | str | None]:
278
269
  """
279
270
  Get the status of a charge point.
280
271
 
@@ -300,7 +291,9 @@ class BlueCurrentClient:
300
291
  "evse_id": "BCU123456",
301
292
  }
302
293
  """
303
- response = get(
294
+ if self.httpx_client is None:
295
+ raise RuntimeError(f"{self.__class__.__name__} is not connected.")
296
+ response = await self.httpx_client.get(
304
297
  f"{self.api_url}/chargepointstatus?evse_id={evse_id}",
305
298
  headers={"Authorization": f"Token {self.token}", "User-Agent": self._user_agent},
306
299
  )
@@ -314,7 +307,7 @@ class BlueCurrentClient:
314
307
  },
315
308
  )
316
309
 
317
- def get_contracts(self) -> list[dict[str, str]]:
310
+ async def get_contracts(self) -> list[dict[str, str]]:
318
311
  """
319
312
  Get your contracts.
320
313
 
@@ -330,14 +323,16 @@ class BlueCurrentClient:
330
323
  }
331
324
  ]
332
325
  """
333
- response = get(
326
+ if self.httpx_client is None:
327
+ raise RuntimeError(f"{self.__class__.__name__} is not connected.")
328
+ response = await self.httpx_client.get(
334
329
  f"{self.api_url}/getcontracts",
335
330
  headers={"Authorization": f"Token {self.token}", "User-Agent": self._user_agent},
336
331
  )
337
332
  response.raise_for_status()
338
333
  return response.json()["contracts"]
339
334
 
340
- def get_grids(self) -> list[dict[str, bool | dict[str, str] | str]]:
335
+ async def get_grids(self) -> list[dict[str, bool | dict[str, str] | str]]:
341
336
  """
342
337
  Get your grid connections.
343
338
 
@@ -352,14 +347,16 @@ class BlueCurrentClient:
352
347
  }
353
348
  ]
354
349
  """
355
- response = get(
350
+ if self.httpx_client is None:
351
+ raise RuntimeError(f"{self.__class__.__name__} is not connected.")
352
+ response = await self.httpx_client.get(
356
353
  f"{self.api_url}/getgrids",
357
354
  headers={"Authorization": f"Token {self.token}", "User-Agent": self._user_agent},
358
355
  )
359
356
  response.raise_for_status()
360
357
  return response.json()["grids"]
361
358
 
362
- def get_transactions(
359
+ async def get_transactions(
363
360
  self, evse_id: str, newest_first: bool = True, page: int = 1
364
361
  ) -> dict[str, int | list[dict[str, Any]]]:
365
362
  """
@@ -398,13 +395,15 @@ class BlueCurrentClient:
398
395
  }
399
396
 
400
397
  """
401
- response = post(
398
+ if self.httpx_client is None:
399
+ raise RuntimeError(f"{self.__class__.__name__} is not connected.")
400
+ response = await self.httpx_client.post(
402
401
  f"{self.api_url}/gettransactions?"
403
402
  f"page={page}&"
404
403
  f"sort_field_order={'DESC' if newest_first else 'ASC'}&"
405
404
  f"sort_field=stoppedtimestamp",
406
405
  headers={"Authorization": f"Token {self.token}", "User-Agent": self._user_agent},
407
- data=dumps({"chargepoints": [{"chargepoint_id": evse_id}]}).encode("UTF-8"),
406
+ data=dumps({"chargepoints": [{"chargepoint_id": evse_id}]}),
408
407
  )
409
408
  response.raise_for_status()
410
409
  result = response.json()["data"]
@@ -414,7 +413,7 @@ class BlueCurrentClient:
414
413
  )
415
414
  return result
416
415
 
417
- def iterate_transactions(self, evse_id: str, newest_first: bool = True) -> Iterable[dict[str, Any]]:
416
+ async def iterate_transactions(self, evse_id: str, newest_first: bool = True) -> AsyncIterable[dict[str, Any]]:
418
417
  """
419
418
  Iterate through your transactions.
420
419
 
@@ -443,8 +442,9 @@ class BlueCurrentClient:
443
442
  """
444
443
  next_page = 1
445
444
  while next_page is not None:
446
- transactions = self.get_transactions(evse_id=evse_id, newest_first=newest_first, page=next_page)
447
- yield from transactions["transactions"] # type: ignore
445
+ transactions = await self.get_transactions(evse_id=evse_id, newest_first=newest_first, page=next_page)
446
+ for tx in transactions["transactions"]: # type: ignore
447
+ yield tx
448
448
  next_page = transactions["next_page"] # type: ignore
449
449
 
450
450
  async def _login(self) -> None:
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: pybluecurrent
3
- Version: 0.0.3
3
+ Version: 0.1.0
4
4
  Summary: Python client for BlueCurrent charge points.
5
5
  Author-email: Rogier van der Geer <rogier@vander-geer.nl>
6
6
  License: MIT
@@ -15,14 +15,15 @@ Requires-Python: >=3.10
15
15
  Description-Content-Type: text/markdown
16
16
  License-File: LICENSE
17
17
  Requires-Dist: asyncio-multisubscriber-queue>=0.4.1
18
- Requires-Dist: requests>=2.31.0
18
+ Requires-Dist: httpx>=0.28
19
19
  Requires-Dist: sjcl>=0.2.1
20
- Requires-Dist: websockets>=11.0.3
20
+ Requires-Dist: websockets>=14.0
21
21
  Provides-Extra: dev
22
22
  Requires-Dist: black==23.3.0; extra == "dev"
23
23
  Requires-Dist: pre-commit>=3.3.3; extra == "dev"
24
- Requires-Dist: pytest==8.1.2; extra == "dev"
25
- Requires-Dist: pytest-asyncio==0.21.1; extra == "dev"
24
+ Requires-Dist: pytest==8.4.2; extra == "dev"
25
+ Requires-Dist: pytest-asyncio==1.2.0; extra == "dev"
26
+ Dynamic: license-file
26
27
 
27
28
  # pybluecurrent
28
29
 
@@ -42,36 +43,32 @@ from pybluecurrent import BlueCurrentClient
42
43
  client = BlueCurrentClient("your_username", "your_secret_password")
43
44
 
44
45
  async with client:
45
- charge_points = await client.get_charge_points()
46
-
47
- transactions = client.get_transactions(charge_points[0]["evse_id"])
46
+ charge_points = await client.get_charge_points()
47
+ transactions = await client.get_transactions(charge_points[0]["evse_id"])
48
48
  ```
49
49
 
50
50
  ## Methods
51
51
 
52
- BlueCurrent exposes two APIs, a synchronous REST API as well as an asynchronous Websocket API.
53
- As a result, the `BlueCurrentClient` also exposes synchronous as well as asynchronous methods:
52
+ The `BlueCurrentClient` exposes the following methods:
54
53
 
55
- - [Asynchronous](#asynchronous)
56
- - [`get_account`](#getaccount---get-your-account-information)
57
- - [`get_charge_cards`](#getchargecards---get-your-charge-cards)
58
- - [`get_charge_points`](#getchargepoints---get-your-charge-points)
59
- - [`get_charge_point_settings`](#getchargepointsettings---get-the-settings-of-a-charge-point)
60
- - [`get_grid_status`](#getgridstatus---get-the-grid-status-associated-to-a-charge-point)
61
- - [`get_sustainability_status`](#getsustainabilitystatus---get-statistics-on-the-sustainability-of-all-your-charge-points)
62
- - [`set_plug_and_charge_charge_card`](#setplugandchargechargecard---set-the-charge-card-for-plug-and-charge)
63
- - [`set_status`](#setstatus---enable-or-disable-a-charge-point)
64
- - [Synchronous](#synchronous)
65
- - [`login`](#login---log-in)
66
- - [`get_charge_point_status`](#getchargepointstatus---get-the-status-of-a-charge-point)
67
- - [`get_contracts`](#getcontracts---get-your-contracts)
68
- - [`get_grids`](#getgrids---get-your-grid-connections)
69
- - [`get_transactions`](#gettransactions---get-a-list-of-transactions)
70
- - [`iterate_transactions`](#iteratetransactions---iterate-through-your-transactions)
71
-
72
- ### Asynchronous
73
-
74
- The async methods can only be used when the websocket client is connected. For example:
54
+ - [`get_account`](#getaccount---get-your-account-information)
55
+ - [`get_charge_cards`](#getchargecards---get-your-charge-cards)
56
+ - [`get_charge_points`](#getchargepoints---get-your-charge-points)
57
+ - [`get_charge_point_settings`](#getchargepointsettings---get-the-settings-of-a-charge-point)
58
+ - [`get_grid_status`](#getgridstatus---get-the-grid-status-associated-to-a-charge-point)
59
+ - [`get_sustainability_status`](#getsustainabilitystatus---get-statistics-on-the-sustainability-of-all-your-charge-points)
60
+ - [`set_plug_and_charge_charge_card`](#setplugandchargechargecard---set-the-charge-card-for-plug-and-charge)
61
+ - [`set_status`](#setstatus---enable-or-disable-a-charge-point)
62
+ - [`login`](#login---log-in)
63
+ - [`get_charge_point_status`](#getchargepointstatus---get-the-status-of-a-charge-point)
64
+ - [`get_contracts`](#getcontracts---get-your-contracts)
65
+ - [`get_grids`](#getgrids---get-your-grid-connections)
66
+ - [`get_transactions`](#gettransactions---get-a-list-of-transactions)
67
+ - [`iterate_transactions`](#iteratetransactions---iterate-through-your-transactions)
68
+
69
+ ### Connection
70
+
71
+ The client can only be used when the websocket client is connected. For example:
75
72
  ```python
76
73
  client = BlueCurrentClient("your_username", "your_secret_password")
77
74
  async with client:
@@ -247,22 +244,10 @@ async def set_status(self, evse_id: str, enabled: bool) -> None
247
244
  - `evse_id`: The ID of the charge point.
248
245
  - `enabled`: Boolean that indicates the desired status.
249
246
 
250
- ### Synchronous
251
-
252
- #### `login` - Log in
253
- ```python
254
- def login(self) -> None
255
- ```
256
-
257
- This method does not do anything if the client is already logged in.
258
- Connection to the websocket api (async with client) automatically logs in the client,
259
- so this endpoint is not needed when using the async API.
260
-
261
-
262
247
  #### `get_charge_point_status` - Get the status of a charge point.
263
248
 
264
249
  ```python
265
- def get_charge_point_status(self, evse_id: str) -> dict[str, datetime | float | int | str | None]
250
+ async def get_charge_point_status(self, evse_id: str) -> dict[str, datetime | float | int | str | None]
266
251
  ```
267
252
 
268
253
  ##### Arguments
@@ -295,7 +280,7 @@ A dictionary with the chargepoint status:
295
280
  #### `get_contracts` - Get your contracts.
296
281
 
297
282
  ```python
298
- def get_contracts(self) -> list[dict[str, str]]
283
+ async def get_contracts(self) -> list[dict[str, str]]
299
284
  ```
300
285
 
301
286
  ##### Returns
@@ -315,7 +300,7 @@ A list of dictionaries, each representing a contract:
315
300
  #### `get_grids` - Get your grid connections.
316
301
 
317
302
  ```python
318
- def get_grids(self) -> list[dict[str, bool | dict[str, str] | str]]
303
+ async def get_grids(self) -> list[dict[str, bool | dict[str, str] | str]]
319
304
  ```
320
305
 
321
306
  ##### Returns
@@ -334,7 +319,7 @@ A list of dictionaries, each representing a grid:
334
319
  #### `get_transactions` - Get a list of transactions.
335
320
 
336
321
  ```python
337
- def get_transactions(
322
+ async def get_transactions(
338
323
  self, evse_id: str, newest_first: bool = True, page: int = 1
339
324
  ) -> dict[str, int | list[dict[str, Any]]]
340
325
  ```
@@ -376,7 +361,7 @@ A dictionary like this:
376
361
  #### `iterate_transactions` - Iterate through your transactions
377
362
 
378
363
  ```python
379
- def iterate_transactions(self, evse_id: str, newest_first: bool = True) -> Iterable[dict[str, Any]]
364
+ async def iterate_transactions(self, evse_id: str, newest_first: bool = True) -> AsyncIterable[dict[str, Any]]
380
365
  ```
381
366
 
382
367
  ##### Arguments
@@ -1,10 +1,10 @@
1
1
  asyncio-multisubscriber-queue>=0.4.1
2
- requests>=2.31.0
2
+ httpx>=0.28
3
3
  sjcl>=0.2.1
4
- websockets>=11.0.3
4
+ websockets>=14.0
5
5
 
6
6
  [dev]
7
7
  black==23.3.0
8
8
  pre-commit>=3.3.3
9
- pytest==8.1.2
10
- pytest-asyncio==0.21.1
9
+ pytest==8.4.2
10
+ pytest-asyncio==1.2.0
@@ -1,9 +1,7 @@
1
- from asyncio import get_event_loop_policy, run
2
1
  from os import environ
3
2
  from typing import AsyncGenerator
4
3
 
5
4
  from pytest import fixture, skip
6
- from pytest_asyncio import fixture as async_fixture
7
5
 
8
6
  from pybluecurrent import BlueCurrentClient
9
7
 
@@ -22,14 +20,6 @@ def client_with_auth() -> BlueCurrentClient | None:
22
20
 
23
21
 
24
22
  @fixture(scope="session")
25
- def event_loop():
26
- policy = get_event_loop_policy()
27
- loop = policy.new_event_loop()
28
- yield loop
29
- loop.close()
30
-
31
-
32
- @async_fixture(scope="session")
33
23
  async def connected_client() -> AsyncGenerator[BlueCurrentClient, None]:
34
24
  try:
35
25
  client = BlueCurrentClient(environ["BLUECURRENT_USERNAME"], environ["BLUECURRENT_PASSWORD"])
@@ -41,13 +31,8 @@ async def connected_client() -> AsyncGenerator[BlueCurrentClient, None]:
41
31
 
42
32
 
43
33
  @fixture(scope="session")
44
- def evse_id(client_with_auth: BlueCurrentClient) -> str:
45
- async def _get_charge_points(client: BlueCurrentClient) -> list:
46
- async with client:
47
- charge_points = await client.get_charge_points()
48
- return charge_points
49
-
50
- result: list = run(_get_charge_points(client_with_auth))
51
- if len(result) == 0:
34
+ async def evse_id(connected_client: BlueCurrentClient) -> str:
35
+ charge_points = await connected_client.get_charge_points()
36
+ if not charge_points:
52
37
  skip("No charge points available.")
53
- return result[0]["evse_id"]
38
+ return charge_points[0]["evse_id"] # type: ignore
@@ -1,8 +1,7 @@
1
1
  from datetime import date
2
- from itertools import islice
3
2
  from os import environ
4
3
 
5
- from pytest import fixture, mark, raises, skip
4
+ from pytest import mark, raises, skip
6
5
 
7
6
  from pybluecurrent import BlueCurrentClient
8
7
  from pybluecurrent.exceptions import AuthenticationFailed, BlueCurrentException
@@ -17,30 +16,22 @@ class TestHeaders:
17
16
 
18
17
 
19
18
  class TestAuthentication:
20
- @mark.asyncio
21
19
  async def test_authenticate(self, client_with_auth: BlueCurrentClient):
22
20
  async with client_with_auth:
23
21
  assert client_with_auth.token is not None
24
22
 
25
- @mark.asyncio
26
23
  async def test_authentication_failed(self, client: BlueCurrentClient):
27
24
  with raises(AuthenticationFailed):
28
25
  async with client:
29
26
  pass
30
27
 
31
- def test_login(self, client_with_auth: BlueCurrentClient):
32
- client_with_auth.login()
33
- assert client_with_auth.token is not None
34
-
35
28
 
36
29
  class TestSocketApi:
37
- @mark.asyncio
38
30
  async def test_get_account(self, connected_client: BlueCurrentClient):
39
31
  account = await connected_client.get_account()
40
32
  assert "full_name" in account
41
33
  assert isinstance(account["first_login_app"], date)
42
34
 
43
- @mark.asyncio
44
35
  async def test_get_charge_cards(self, connected_client: BlueCurrentClient):
45
36
  charge_cards = await connected_client.get_charge_cards()
46
37
  if len(charge_cards) == 0:
@@ -56,7 +47,6 @@ class TestSocketApi:
56
47
  ]
57
48
  )
58
49
 
59
- @mark.asyncio
60
50
  async def test_get_charge_points(self, connected_client: BlueCurrentClient):
61
51
  charge_points = await connected_client.get_charge_points()
62
52
  if len(charge_points) == 0:
@@ -64,41 +54,34 @@ class TestSocketApi:
64
54
  for charge_point in charge_points:
65
55
  assert "evse_id" in charge_point
66
56
 
67
- @mark.asyncio
68
57
  async def test_get_grid_status(self, connected_client: BlueCurrentClient, evse_id: str):
69
58
  status = await connected_client.get_grid_status(evse_id=evse_id)
70
59
  assert "grid_actual_p1" in status
71
60
  assert "id" in status
72
61
 
73
- @mark.asyncio
74
62
  async def test_get_charge_point_settings(self, connected_client: BlueCurrentClient, evse_id: str):
75
63
  settings = await connected_client.get_charge_point_settings(evse_id=evse_id)
76
64
  assert isinstance(settings, dict)
77
65
  assert settings["evse_id"] == evse_id
78
66
 
79
- @mark.asyncio
80
67
  @mark.skip("Does not work")
81
68
  async def test_get_sessions(self, connected_client: BlueCurrentClient, evse_id: str):
82
69
  sessions = await connected_client.get_sessions(evse_id=evse_id)
83
70
  print(sessions)
84
71
 
85
- @mark.asyncio
86
72
  async def test_get_sustainability_status(self, connected_client: BlueCurrentClient):
87
73
  sessions = await connected_client.get_sustainability_status()
88
74
  assert set(sessions.keys()) == {"trees", "co2"}
89
75
 
90
- @mark.asyncio
91
76
  @mark.skip("Does not work.")
92
77
  async def test_unlock_connector(self, connected_client: BlueCurrentClient, evse_id: str):
93
78
  result = await connected_client.unlock_connector(evse_id=evse_id)
94
79
  print(result)
95
80
 
96
- @mark.asyncio
97
81
  @mark.skip("Do not change chargepoint status.")
98
82
  async def test_soft_reset(self, connected_client: BlueCurrentClient, evse_id: str):
99
83
  _ = await connected_client.soft_reset(evse_id=evse_id)
100
84
 
101
- @mark.asyncio
102
85
  @mark.skipif(environ.get("BLUECURRENT_READ_ONLY", "TRUE") != "FALSE", reason="Running read-only tests.")
103
86
  async def test_set_plug_and_charge_card(self, connected_client: BlueCurrentClient, evse_id: str):
104
87
  async def _get_plug_and_charge_card_uid() -> str | None:
@@ -115,7 +98,7 @@ class TestSocketApi:
115
98
  charge_cards = await connected_client.get_charge_cards()
116
99
  if len(charge_cards) == 0:
117
100
  skip(reason="No charge cards.")
118
- uids: list[str | None] = [charge_card["uid"] for charge_card in charge_cards] + [None] # type: ignore
101
+ uids: list[str | None] = [charge_card["uid"] for charge_card in charge_cards] + ["BCU_HOME_USE"] # type: ignore
119
102
  # Set each card as plug_and_charge_card
120
103
  for uid in uids:
121
104
  if uid != before_card:
@@ -125,7 +108,6 @@ class TestSocketApi:
125
108
  await connected_client.set_plug_and_charge_charge_card(evse_id=evse_id, uid=before_card)
126
109
  assert await _get_plug_and_charge_card_uid() == before_card
127
110
 
128
- @mark.asyncio
129
111
  @mark.skipif(environ.get("BLUECURRENT_READ_ONLY", "TRUE") != "FALSE", reason="Running read-only tests.")
130
112
  async def test_set_invalid_plug_and_charge_card(self, connected_client: BlueCurrentClient, evse_id: str):
131
113
  settings = await connected_client.get_charge_point_settings(evse_id=evse_id)
@@ -133,18 +115,16 @@ class TestSocketApi:
133
115
  await connected_client.set_plug_and_charge_charge_card(evse_id=evse_id, uid="INVALID_CARD")
134
116
  assert await connected_client.get_charge_point_settings(evse_id=evse_id) == settings
135
117
 
136
- @mark.asyncio
137
118
  @mark.skipif(environ.get("BLUECURRENT_READ_ONLY", "TRUE") != "FALSE", reason="Running read-only tests.")
138
119
  async def test_set_status(self, connected_client: BlueCurrentClient, evse_id: str):
139
- before_status = connected_client.get_charge_point_status(evse_id=evse_id)
120
+ before_status = await connected_client.get_charge_point_status(evse_id=evse_id)
140
121
  if before_status["activity"] != "available":
141
122
  skip(reason="Only perform this test if the charge point is available.")
142
123
  await connected_client.set_status(evse_id=evse_id, enabled=False)
143
- assert connected_client.get_charge_point_status(evse_id=evse_id)["activity"] == "unavailable"
124
+ assert (await connected_client.get_charge_point_status(evse_id=evse_id))["activity"] == "unavailable"
144
125
  await connected_client.set_status(evse_id=evse_id, enabled=True)
145
- assert connected_client.get_charge_point_status(evse_id=evse_id)["activity"] == "available"
126
+ assert (await connected_client.get_charge_point_status(evse_id=evse_id))["activity"] == "available"
146
127
 
147
- @mark.asyncio
148
128
  async def test_error(self, connected_client: BlueCurrentClient):
149
129
  with raises(BlueCurrentException) as e:
150
130
  await connected_client.set_status("BCU123456", False)
@@ -152,33 +132,36 @@ class TestSocketApi:
152
132
 
153
133
 
154
134
  class TestRestApi:
155
- @fixture(scope="class")
156
- def authenticated_client(self, client_with_auth: BlueCurrentClient) -> BlueCurrentClient:
157
- client_with_auth.login()
158
- return client_with_auth
159
-
160
- def test_get_contracts(self, authenticated_client: BlueCurrentClient):
161
- contracts = authenticated_client.get_contracts()
135
+ async def test_get_contracts(self, connected_client: BlueCurrentClient):
136
+ contracts = await connected_client.get_contracts()
162
137
  assert len(contracts) > 0
163
138
  assert "contract_id" in contracts[0]
164
139
 
165
- def test_get_charge_point_status(self, authenticated_client: BlueCurrentClient, evse_id: str):
166
- status = authenticated_client.get_charge_point_status(evse_id)
140
+ async def test_get_charge_point_status(self, connected_client: BlueCurrentClient, evse_id: str):
141
+ status = await connected_client.get_charge_point_status(evse_id)
167
142
  assert status["evse_id"] == evse_id
168
143
  assert "activity" in status
169
144
 
170
- def test_get_grids(self, authenticated_client: BlueCurrentClient):
171
- grids = authenticated_client.get_grids()
145
+ async def test_get_grids(self, connected_client: BlueCurrentClient):
146
+ grids = await connected_client.get_grids()
172
147
  assert len(grids) > 0
173
148
  assert "id" in grids[0]
174
149
 
175
- def test_get_transactions(self, authenticated_client: BlueCurrentClient, evse_id: str):
176
- transactions = authenticated_client.get_transactions(evse_id)
150
+ async def test_get_transactions(self, connected_client: BlueCurrentClient, evse_id: str):
151
+ transactions = await connected_client.get_transactions(evse_id)
177
152
  assert "transactions" in transactions
178
153
 
179
- def test_iterate_transactions(self, authenticated_client: BlueCurrentClient, evse_id: str):
180
- transactions = authenticated_client.get_transactions(evse_id)
181
- # If there are less than three pages, there might be less than 30.
154
+ async def test_iterate_transactions(self, connected_client: BlueCurrentClient, evse_id: str):
155
+ transactions = await connected_client.get_transactions(evse_id)
156
+ # If there are less than three pages, there might be fewer than 30.
182
157
  if transactions["total_pages"] < 3: # type: ignore
183
158
  skip("Not enough transactions.")
184
- assert len({t["transaction_id"] for t in islice(authenticated_client.iterate_transactions(evse_id), 30)}) == 30
159
+ n_transactions = 0
160
+ # Verify pagination works correctly - we get 30 unique transactions from multiple pages.
161
+ unique_transactions = set()
162
+ async for transaction in connected_client.iterate_transactions(evse_id):
163
+ n_transactions += 1
164
+ unique_transactions.add(transaction["transaction_id"])
165
+ if n_transactions >= 30:
166
+ break
167
+ assert len(unique_transactions) == 30
@@ -1,16 +0,0 @@
1
- # file generated by setuptools_scm
2
- # don't change, don't track in version control
3
- TYPE_CHECKING = False
4
- if TYPE_CHECKING:
5
- from typing import Tuple, Union
6
- VERSION_TUPLE = Tuple[Union[int, str], ...]
7
- else:
8
- VERSION_TUPLE = object
9
-
10
- version: str
11
- __version__: str
12
- __version_tuple__: VERSION_TUPLE
13
- version_tuple: VERSION_TUPLE
14
-
15
- __version__ = version = '0.0.3'
16
- __version_tuple__ = version_tuple = (0, 0, 3)
File without changes
File without changes
File without changes