leneda-client 0.2.0__tar.gz → 0.4.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.
- leneda_client-0.4.0/LICENSE +21 -0
- {leneda_client-0.2.0 → leneda_client-0.4.0}/PKG-INFO +37 -4
- leneda_client-0.4.0/README.md +52 -0
- {leneda_client-0.2.0 → leneda_client-0.4.0}/src/leneda/client.py +85 -1
- leneda_client-0.4.0/src/leneda/exceptions.py +21 -0
- {leneda_client-0.2.0 → leneda_client-0.4.0}/src/leneda/version.py +1 -1
- {leneda_client-0.2.0 → leneda_client-0.4.0}/src/leneda_client.egg-info/PKG-INFO +37 -4
- {leneda_client-0.2.0 → leneda_client-0.4.0}/src/leneda_client.egg-info/SOURCES.txt +2 -0
- {leneda_client-0.2.0 → leneda_client-0.4.0}/tests/test_client.py +144 -1
- leneda_client-0.2.0/README.md +0 -21
- {leneda_client-0.2.0 → leneda_client-0.4.0}/MANIFEST.in +0 -0
- {leneda_client-0.2.0 → leneda_client-0.4.0}/examples/advanced_usage.py +0 -0
- {leneda_client-0.2.0 → leneda_client-0.4.0}/examples/basic_usage.py +0 -0
- {leneda_client-0.2.0 → leneda_client-0.4.0}/pyproject.toml +0 -0
- {leneda_client-0.2.0 → leneda_client-0.4.0}/requirements.txt +0 -0
- {leneda_client-0.2.0 → leneda_client-0.4.0}/setup.cfg +0 -0
- {leneda_client-0.2.0 → leneda_client-0.4.0}/setup.py +0 -0
- {leneda_client-0.2.0 → leneda_client-0.4.0}/src/leneda/__init__.py +0 -0
- {leneda_client-0.2.0 → leneda_client-0.4.0}/src/leneda/models.py +0 -0
- {leneda_client-0.2.0 → leneda_client-0.4.0}/src/leneda/obis_codes.py +0 -0
- {leneda_client-0.2.0 → leneda_client-0.4.0}/src/leneda_client.egg-info/dependency_links.txt +0 -0
- {leneda_client-0.2.0 → leneda_client-0.4.0}/src/leneda_client.egg-info/requires.txt +0 -0
- {leneda_client-0.2.0 → leneda_client-0.4.0}/src/leneda_client.egg-info/top_level.txt +0 -0
- {leneda_client-0.2.0 → leneda_client-0.4.0}/tests/__init__.py +0 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 fedus
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: leneda-client
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.4.0
|
4
4
|
Summary: Python client for the Leneda energy data platform
|
5
5
|
Home-page: https://github.com/fedus/leneda-client
|
6
6
|
Author: fedus
|
@@ -21,6 +21,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
21
|
Classifier: Topic :: Utilities
|
22
22
|
Requires-Python: >=3.8
|
23
23
|
Description-Content-Type: text/markdown
|
24
|
+
License-File: LICENSE
|
24
25
|
Requires-Dist: requests>=2.25.0
|
25
26
|
Requires-Dist: python-dateutil>=2.8.2
|
26
27
|
Dynamic: author
|
@@ -30,6 +31,7 @@ Dynamic: description
|
|
30
31
|
Dynamic: description-content-type
|
31
32
|
Dynamic: home-page
|
32
33
|
Dynamic: keywords
|
34
|
+
Dynamic: license-file
|
33
35
|
Dynamic: project-url
|
34
36
|
Dynamic: requires-dist
|
35
37
|
Dynamic: requires-python
|
@@ -37,9 +39,10 @@ Dynamic: summary
|
|
37
39
|
|
38
40
|
# Leneda API Client
|
39
41
|
|
40
|
-
[![PyPI version]](https://pypi.org/project/leneda-client/)
|
41
|
-
[![Python
|
42
|
-
[![License]](https://github.com/fedus/leneda-client/blob/main/LICENSE)
|
42
|
+
[](https://pypi.org/project/leneda-client/)
|
43
|
+
[](https://pypi.org/project/leneda-client/)
|
44
|
+
[](https://github.com/fedus/leneda-client/blob/main/LICENSE)
|
45
|
+
|
43
46
|
|
44
47
|
A Python client for interacting with the Leneda energy data platform API.
|
45
48
|
|
@@ -56,3 +59,33 @@ This client provides a simple interface to the Leneda API, which allows users to
|
|
56
59
|
|
57
60
|
```bash
|
58
61
|
pip install leneda-client
|
62
|
+
```
|
63
|
+
|
64
|
+
## Trying it out
|
65
|
+
|
66
|
+
```bash
|
67
|
+
$ export LENEDA_ENERGY_ID='LUXE-xx-yy-1234'
|
68
|
+
$ export LENEDA_API_KEY='YOUR-API-KEY'
|
69
|
+
$ python examples/basic_usage.py --metering-point LU0000012345678901234000000000000
|
70
|
+
Example 1: Getting hourly electricity consumption data for the last 7 days
|
71
|
+
Retrieved 514 consumption measurements
|
72
|
+
Unit: kW
|
73
|
+
Interval length: PT15M
|
74
|
+
Metering point: LU0000012345678901234000000000000
|
75
|
+
OBIS code: ObisCode.ELEC_CONSUMPTION_ACTIVE
|
76
|
+
|
77
|
+
First 3 measurements:
|
78
|
+
Time: 2025-04-18T13:30:00+00:00, Value: 0.048 kW, Type: Actual, Version: 2, Calculated: False
|
79
|
+
Time: 2025-04-18T13:45:00+00:00, Value: 0.08 kW, Type: Actual, Version: 2, Calculated: False
|
80
|
+
Time: 2025-04-18T14:00:00+00:00, Value: 0.08 kW, Type: Actual, Version: 2, Calculated: False
|
81
|
+
|
82
|
+
Example 2: Getting monthly aggregated electricity consumption for 2025
|
83
|
+
Retrieved 4 monthly aggregations
|
84
|
+
Unit: kWh
|
85
|
+
|
86
|
+
Monthly consumption:
|
87
|
+
Period: 2024-12 to 2025-01, Value: 30.858 kWh, Calculated: False
|
88
|
+
Period: 2025-01 to 2025-02, Value: 148.985 kWh, Calculated: False
|
89
|
+
Period: 2025-02 to 2025-03, Value: 44.619 kWh, Calculated: False
|
90
|
+
Period: 2025-03 to 2025-04, Value: 29.662 kWh, Calculated: False
|
91
|
+
```
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# Leneda API Client
|
2
|
+
|
3
|
+
[](https://pypi.org/project/leneda-client/)
|
4
|
+
[](https://pypi.org/project/leneda-client/)
|
5
|
+
[](https://github.com/fedus/leneda-client/blob/main/LICENSE)
|
6
|
+
|
7
|
+
|
8
|
+
A Python client for interacting with the Leneda energy data platform API.
|
9
|
+
|
10
|
+
## Overview
|
11
|
+
|
12
|
+
This client provides a simple interface to the Leneda API, which allows users to:
|
13
|
+
|
14
|
+
- Retrieve metering data for specific time ranges
|
15
|
+
- Get aggregated metering data (hourly, daily, weekly, monthly, or total)
|
16
|
+
- Create metering data access requests
|
17
|
+
- Use predefined OBIS code constants for easy reference
|
18
|
+
|
19
|
+
## Installation
|
20
|
+
|
21
|
+
```bash
|
22
|
+
pip install leneda-client
|
23
|
+
```
|
24
|
+
|
25
|
+
## Trying it out
|
26
|
+
|
27
|
+
```bash
|
28
|
+
$ export LENEDA_ENERGY_ID='LUXE-xx-yy-1234'
|
29
|
+
$ export LENEDA_API_KEY='YOUR-API-KEY'
|
30
|
+
$ python examples/basic_usage.py --metering-point LU0000012345678901234000000000000
|
31
|
+
Example 1: Getting hourly electricity consumption data for the last 7 days
|
32
|
+
Retrieved 514 consumption measurements
|
33
|
+
Unit: kW
|
34
|
+
Interval length: PT15M
|
35
|
+
Metering point: LU0000012345678901234000000000000
|
36
|
+
OBIS code: ObisCode.ELEC_CONSUMPTION_ACTIVE
|
37
|
+
|
38
|
+
First 3 measurements:
|
39
|
+
Time: 2025-04-18T13:30:00+00:00, Value: 0.048 kW, Type: Actual, Version: 2, Calculated: False
|
40
|
+
Time: 2025-04-18T13:45:00+00:00, Value: 0.08 kW, Type: Actual, Version: 2, Calculated: False
|
41
|
+
Time: 2025-04-18T14:00:00+00:00, Value: 0.08 kW, Type: Actual, Version: 2, Calculated: False
|
42
|
+
|
43
|
+
Example 2: Getting monthly aggregated electricity consumption for 2025
|
44
|
+
Retrieved 4 monthly aggregations
|
45
|
+
Unit: kWh
|
46
|
+
|
47
|
+
Monthly consumption:
|
48
|
+
Period: 2024-12 to 2025-01, Value: 30.858 kWh, Calculated: False
|
49
|
+
Period: 2025-01 to 2025-02, Value: 148.985 kWh, Calculated: False
|
50
|
+
Period: 2025-02 to 2025-03, Value: 44.619 kWh, Calculated: False
|
51
|
+
Period: 2025-03 to 2025-04, Value: 29.662 kWh, Calculated: False
|
52
|
+
```
|
@@ -7,11 +7,12 @@ energy consumption and production data for electricity and gas.
|
|
7
7
|
|
8
8
|
import json
|
9
9
|
import logging
|
10
|
-
from datetime import datetime
|
10
|
+
from datetime import datetime, timedelta
|
11
11
|
from typing import Any, Dict, List, Optional, Union
|
12
12
|
|
13
13
|
import requests
|
14
14
|
|
15
|
+
from .exceptions import ForbiddenException, UnauthorizedException
|
15
16
|
from .models import (
|
16
17
|
AggregatedMeteringData,
|
17
18
|
MeteringData,
|
@@ -70,6 +71,12 @@ class LenedaClient:
|
|
70
71
|
|
71
72
|
Returns:
|
72
73
|
The JSON response from the API
|
74
|
+
|
75
|
+
Raises:
|
76
|
+
UnauthorizedException: If the API returns a 401 status code
|
77
|
+
ForbiddenException: If the API returns a 403 status code
|
78
|
+
requests.exceptions.RequestException: For other request errors
|
79
|
+
json.JSONDecodeError: If the response cannot be parsed as JSON
|
73
80
|
"""
|
74
81
|
url = f"{self.BASE_URL}/{endpoint}"
|
75
82
|
|
@@ -87,6 +94,14 @@ class LenedaClient:
|
|
87
94
|
)
|
88
95
|
|
89
96
|
# Check for HTTP errors
|
97
|
+
if response.status_code == 401:
|
98
|
+
raise UnauthorizedException(
|
99
|
+
"API authentication failed. Please check your API key and energy ID."
|
100
|
+
)
|
101
|
+
if response.status_code == 403:
|
102
|
+
raise ForbiddenException(
|
103
|
+
"Access forbidden. This may be due to Leneda's geoblocking or other access restrictions."
|
104
|
+
)
|
90
105
|
response.raise_for_status()
|
91
106
|
|
92
107
|
# Parse the response
|
@@ -234,3 +249,72 @@ class LenedaClient:
|
|
234
249
|
response_data = self._make_request(method="POST", endpoint=endpoint, json_data=data)
|
235
250
|
|
236
251
|
return response_data
|
252
|
+
|
253
|
+
def probe_metering_point_obis_code(self, metering_point_code: str, obis_code: ObisCode) -> bool:
|
254
|
+
"""
|
255
|
+
Probe if a metering point provides data for a specific OBIS code.
|
256
|
+
|
257
|
+
NOTE: This method is essentially a best guess since the Leneda API does not provide a way to check
|
258
|
+
if a metering point provides data for a specific OBIS code or whether a metering point code is valid
|
259
|
+
|
260
|
+
This method checks if a metering point provides data for the specified OBIS code by making a request
|
261
|
+
for aggregated metering data. If the unit property in the response is null, it indicates that either:
|
262
|
+
- The metering point is invalid, or
|
263
|
+
- The metering point does not provide data for the specified OBIS code
|
264
|
+
|
265
|
+
Args:
|
266
|
+
metering_point_code: The metering point code to probe
|
267
|
+
obis_code: The OBIS code to check for data availability
|
268
|
+
|
269
|
+
Returns:
|
270
|
+
bool: True if the metering point provides data for the specified OBIS code, False otherwise
|
271
|
+
|
272
|
+
Raises:
|
273
|
+
UnauthorizedException: If the API returns a 401 status code
|
274
|
+
ForbiddenException: If the API returns a 403 status code
|
275
|
+
requests.exceptions.RequestException: For other request errors
|
276
|
+
"""
|
277
|
+
# Use arbitrary time window
|
278
|
+
end_date = datetime.now()
|
279
|
+
start_date = end_date - timedelta(weeks=4)
|
280
|
+
|
281
|
+
# Try to get aggregated data for the specified OBIS code
|
282
|
+
result = self.get_aggregated_metering_data(
|
283
|
+
metering_point_code=metering_point_code,
|
284
|
+
obis_code=obis_code,
|
285
|
+
start_date=start_date,
|
286
|
+
end_date=end_date,
|
287
|
+
aggregation_level="Month",
|
288
|
+
transformation_mode="Accumulation",
|
289
|
+
)
|
290
|
+
|
291
|
+
# Return True if we got data (unit is not None), False otherwise
|
292
|
+
return result.unit is not None
|
293
|
+
|
294
|
+
def get_supported_obis_codes(self, metering_point_code: str) -> List[ObisCode]:
|
295
|
+
"""
|
296
|
+
Get all OBIS codes that are supported by a given metering point.
|
297
|
+
|
298
|
+
NOTE: Please see the documentation of the probe_metering_point_obis_code method about best guess
|
299
|
+
behaviour. If this method returns an empty list, chances are high that the metering point code
|
300
|
+
is invalid or that the Energy ID has no access to it.
|
301
|
+
|
302
|
+
This method probes each OBIS code defined in the ObisCode enum to determine
|
303
|
+
which ones are supported by the specified metering point.
|
304
|
+
|
305
|
+
Args:
|
306
|
+
metering_point_code: The metering point code to check
|
307
|
+
|
308
|
+
Returns:
|
309
|
+
List[ObisCode]: A list of OBIS codes that are supported by the metering point
|
310
|
+
|
311
|
+
Raises:
|
312
|
+
UnauthorizedException: If the API returns a 401 status code
|
313
|
+
ForbiddenException: If the API returns a 403 status code
|
314
|
+
requests.exceptions.RequestException: For other request errors
|
315
|
+
"""
|
316
|
+
return [
|
317
|
+
obis_code
|
318
|
+
for obis_code in ObisCode
|
319
|
+
if self.probe_metering_point_obis_code(metering_point_code, obis_code)
|
320
|
+
]
|
@@ -0,0 +1,21 @@
|
|
1
|
+
"""
|
2
|
+
Custom exceptions for the Leneda API client.
|
3
|
+
"""
|
4
|
+
|
5
|
+
|
6
|
+
class LenedaException(Exception):
|
7
|
+
"""Base exception for all Leneda API client exceptions."""
|
8
|
+
|
9
|
+
pass
|
10
|
+
|
11
|
+
|
12
|
+
class UnauthorizedException(LenedaException):
|
13
|
+
"""Raised when API authentication fails (401 Unauthorized)."""
|
14
|
+
|
15
|
+
pass
|
16
|
+
|
17
|
+
|
18
|
+
class ForbiddenException(LenedaException):
|
19
|
+
"""Raised when access is forbidden (403 Forbidden), typically due to geoblocking or other access restrictions."""
|
20
|
+
|
21
|
+
pass
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: leneda-client
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.4.0
|
4
4
|
Summary: Python client for the Leneda energy data platform
|
5
5
|
Home-page: https://github.com/fedus/leneda-client
|
6
6
|
Author: fedus
|
@@ -21,6 +21,7 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
21
|
Classifier: Topic :: Utilities
|
22
22
|
Requires-Python: >=3.8
|
23
23
|
Description-Content-Type: text/markdown
|
24
|
+
License-File: LICENSE
|
24
25
|
Requires-Dist: requests>=2.25.0
|
25
26
|
Requires-Dist: python-dateutil>=2.8.2
|
26
27
|
Dynamic: author
|
@@ -30,6 +31,7 @@ Dynamic: description
|
|
30
31
|
Dynamic: description-content-type
|
31
32
|
Dynamic: home-page
|
32
33
|
Dynamic: keywords
|
34
|
+
Dynamic: license-file
|
33
35
|
Dynamic: project-url
|
34
36
|
Dynamic: requires-dist
|
35
37
|
Dynamic: requires-python
|
@@ -37,9 +39,10 @@ Dynamic: summary
|
|
37
39
|
|
38
40
|
# Leneda API Client
|
39
41
|
|
40
|
-
[![PyPI version]](https://pypi.org/project/leneda-client/)
|
41
|
-
[![Python
|
42
|
-
[![License]](https://github.com/fedus/leneda-client/blob/main/LICENSE)
|
42
|
+
[](https://pypi.org/project/leneda-client/)
|
43
|
+
[](https://pypi.org/project/leneda-client/)
|
44
|
+
[](https://github.com/fedus/leneda-client/blob/main/LICENSE)
|
45
|
+
|
43
46
|
|
44
47
|
A Python client for interacting with the Leneda energy data platform API.
|
45
48
|
|
@@ -56,3 +59,33 @@ This client provides a simple interface to the Leneda API, which allows users to
|
|
56
59
|
|
57
60
|
```bash
|
58
61
|
pip install leneda-client
|
62
|
+
```
|
63
|
+
|
64
|
+
## Trying it out
|
65
|
+
|
66
|
+
```bash
|
67
|
+
$ export LENEDA_ENERGY_ID='LUXE-xx-yy-1234'
|
68
|
+
$ export LENEDA_API_KEY='YOUR-API-KEY'
|
69
|
+
$ python examples/basic_usage.py --metering-point LU0000012345678901234000000000000
|
70
|
+
Example 1: Getting hourly electricity consumption data for the last 7 days
|
71
|
+
Retrieved 514 consumption measurements
|
72
|
+
Unit: kW
|
73
|
+
Interval length: PT15M
|
74
|
+
Metering point: LU0000012345678901234000000000000
|
75
|
+
OBIS code: ObisCode.ELEC_CONSUMPTION_ACTIVE
|
76
|
+
|
77
|
+
First 3 measurements:
|
78
|
+
Time: 2025-04-18T13:30:00+00:00, Value: 0.048 kW, Type: Actual, Version: 2, Calculated: False
|
79
|
+
Time: 2025-04-18T13:45:00+00:00, Value: 0.08 kW, Type: Actual, Version: 2, Calculated: False
|
80
|
+
Time: 2025-04-18T14:00:00+00:00, Value: 0.08 kW, Type: Actual, Version: 2, Calculated: False
|
81
|
+
|
82
|
+
Example 2: Getting monthly aggregated electricity consumption for 2025
|
83
|
+
Retrieved 4 monthly aggregations
|
84
|
+
Unit: kWh
|
85
|
+
|
86
|
+
Monthly consumption:
|
87
|
+
Period: 2024-12 to 2025-01, Value: 30.858 kWh, Calculated: False
|
88
|
+
Period: 2025-01 to 2025-02, Value: 148.985 kWh, Calculated: False
|
89
|
+
Period: 2025-02 to 2025-03, Value: 44.619 kWh, Calculated: False
|
90
|
+
Period: 2025-03 to 2025-04, Value: 29.662 kWh, Calculated: False
|
91
|
+
```
|
@@ -1,3 +1,4 @@
|
|
1
|
+
LICENSE
|
1
2
|
MANIFEST.in
|
2
3
|
README.md
|
3
4
|
pyproject.toml
|
@@ -7,6 +8,7 @@ examples/advanced_usage.py
|
|
7
8
|
examples/basic_usage.py
|
8
9
|
src/leneda/__init__.py
|
9
10
|
src/leneda/client.py
|
11
|
+
src/leneda/exceptions.py
|
10
12
|
src/leneda/models.py
|
11
13
|
src/leneda/obis_codes.py
|
12
14
|
src/leneda/version.py
|
@@ -14,6 +14,10 @@ import requests
|
|
14
14
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
15
15
|
|
16
16
|
from src.leneda import LenedaClient
|
17
|
+
from src.leneda.exceptions import (
|
18
|
+
ForbiddenException,
|
19
|
+
UnauthorizedException,
|
20
|
+
)
|
17
21
|
from src.leneda.models import (
|
18
22
|
AggregatedMeteringData,
|
19
23
|
AggregatedMeteringValue,
|
@@ -220,9 +224,51 @@ class TestLenedaClient(unittest.TestCase):
|
|
220
224
|
},
|
221
225
|
)
|
222
226
|
|
227
|
+
@patch("requests.request")
|
228
|
+
def test_unauthorized_error(self, mock_request):
|
229
|
+
"""Test handling of 401 Unauthorized errors."""
|
230
|
+
# Set up the mock response with 401 status
|
231
|
+
mock_response = MagicMock()
|
232
|
+
mock_response.status_code = 401
|
233
|
+
mock_response.content = b"Unauthorized"
|
234
|
+
mock_request.return_value = mock_response
|
235
|
+
|
236
|
+
# Call the method and check that it raises UnauthorizedException
|
237
|
+
with self.assertRaises(UnauthorizedException) as context:
|
238
|
+
self.client.get_metering_data(
|
239
|
+
"LU-METERING_POINT1",
|
240
|
+
ObisCode.ELEC_CONSUMPTION_ACTIVE,
|
241
|
+
"2023-01-01T00:00:00Z",
|
242
|
+
"2023-01-02T00:00:00Z",
|
243
|
+
)
|
244
|
+
|
245
|
+
# Check the error message
|
246
|
+
self.assertIn("API authentication failed", str(context.exception))
|
247
|
+
|
248
|
+
@patch("requests.request")
|
249
|
+
def test_forbidden_error(self, mock_request):
|
250
|
+
"""Test handling of 403 Forbidden errors."""
|
251
|
+
# Set up the mock response with 403 status
|
252
|
+
mock_response = MagicMock()
|
253
|
+
mock_response.status_code = 403
|
254
|
+
mock_response.content = b"Forbidden"
|
255
|
+
mock_request.return_value = mock_response
|
256
|
+
|
257
|
+
# Call the method and check that it raises ForbiddenException
|
258
|
+
with self.assertRaises(ForbiddenException) as context:
|
259
|
+
self.client.get_metering_data(
|
260
|
+
"LU-METERING_POINT1",
|
261
|
+
ObisCode.ELEC_CONSUMPTION_ACTIVE,
|
262
|
+
"2023-01-01T00:00:00Z",
|
263
|
+
"2023-01-02T00:00:00Z",
|
264
|
+
)
|
265
|
+
|
266
|
+
# Check the error message
|
267
|
+
self.assertIn("geoblocking", str(context.exception))
|
268
|
+
|
223
269
|
@patch("requests.request")
|
224
270
|
def test_error_handling(self, mock_request):
|
225
|
-
"""Test error handling."""
|
271
|
+
"""Test error handling for other HTTP errors."""
|
226
272
|
# Set up the mock response to raise an exception
|
227
273
|
mock_request.side_effect = requests.exceptions.HTTPError("404 Client Error")
|
228
274
|
|
@@ -235,6 +281,103 @@ class TestLenedaClient(unittest.TestCase):
|
|
235
281
|
"2023-01-02T00:00:00Z",
|
236
282
|
)
|
237
283
|
|
284
|
+
@patch("requests.request")
|
285
|
+
def test_probe_metering_point_obis_code_valid(self, mock_request):
|
286
|
+
"""Test probe_metering_point_obis_code with a valid metering point and OBIS code."""
|
287
|
+
# Set up the mock response with valid data
|
288
|
+
mock_response = MagicMock()
|
289
|
+
mock_response.status_code = 200
|
290
|
+
mock_response.json.return_value = {"unit": "kWh", "aggregatedTimeSeries": []}
|
291
|
+
mock_response.content = json.dumps(mock_response.json.return_value).encode()
|
292
|
+
mock_request.return_value = mock_response
|
293
|
+
|
294
|
+
# Call the method
|
295
|
+
result = self.client.probe_metering_point_obis_code(
|
296
|
+
"LU-METERING_POINT1", ObisCode.ELEC_CONSUMPTION_ACTIVE
|
297
|
+
)
|
298
|
+
|
299
|
+
# Check the result
|
300
|
+
self.assertTrue(result)
|
301
|
+
|
302
|
+
# Check that the request was made correctly
|
303
|
+
mock_request.assert_called_once()
|
304
|
+
|
305
|
+
@patch("requests.request")
|
306
|
+
def test_probe_metering_point_obis_code_invalid(self, mock_request):
|
307
|
+
"""Test probe_metering_point_obis_code with an invalid metering point or unsupported OBIS code."""
|
308
|
+
# Set up the mock response with null unit
|
309
|
+
mock_response = MagicMock()
|
310
|
+
mock_response.status_code = 200
|
311
|
+
mock_response.json.return_value = {"unit": None, "aggregatedTimeSeries": []}
|
312
|
+
mock_response.content = json.dumps(mock_response.json.return_value).encode()
|
313
|
+
mock_request.return_value = mock_response
|
314
|
+
|
315
|
+
# Call the method
|
316
|
+
result = self.client.probe_metering_point_obis_code(
|
317
|
+
"INVALID-METERING-POINT", ObisCode.ELEC_CONSUMPTION_ACTIVE
|
318
|
+
)
|
319
|
+
|
320
|
+
# Check the result
|
321
|
+
self.assertFalse(result)
|
322
|
+
|
323
|
+
# Check that the request was made correctly
|
324
|
+
mock_request.assert_called_once()
|
325
|
+
|
326
|
+
@patch("requests.request")
|
327
|
+
def test_get_supported_obis_codes(self, mock_request):
|
328
|
+
"""Test getting supported OBIS codes for a metering point."""
|
329
|
+
|
330
|
+
# Set up the mock response to return different results for different OBIS codes
|
331
|
+
def mock_response_side_effect(*args, **kwargs):
|
332
|
+
mock_response = MagicMock()
|
333
|
+
mock_response.status_code = 200
|
334
|
+
|
335
|
+
# Check which OBIS code is being probed
|
336
|
+
obis_code = kwargs.get("params", {}).get("obisCode")
|
337
|
+
if obis_code == ObisCode.ELEC_CONSUMPTION_ACTIVE.value:
|
338
|
+
mock_response.json.return_value = {"unit": "kWh", "aggregatedTimeSeries": []}
|
339
|
+
elif obis_code == ObisCode.ELEC_PRODUCTION_ACTIVE.value:
|
340
|
+
mock_response.json.return_value = {"unit": "kWh", "aggregatedTimeSeries": []}
|
341
|
+
else:
|
342
|
+
mock_response.json.return_value = {"unit": None, "aggregatedTimeSeries": []}
|
343
|
+
|
344
|
+
mock_response.content = json.dumps(mock_response.json.return_value).encode()
|
345
|
+
return mock_response
|
346
|
+
|
347
|
+
mock_request.side_effect = mock_response_side_effect
|
348
|
+
|
349
|
+
# Call the method
|
350
|
+
result = self.client.get_supported_obis_codes("LU-METERING_POINT1")
|
351
|
+
|
352
|
+
# Check the result
|
353
|
+
self.assertIsInstance(result, list)
|
354
|
+
self.assertEqual(len(result), 2) # We expect 2 supported OBIS codes
|
355
|
+
self.assertIn(ObisCode.ELEC_CONSUMPTION_ACTIVE, result)
|
356
|
+
self.assertIn(ObisCode.ELEC_PRODUCTION_ACTIVE, result)
|
357
|
+
|
358
|
+
# Check that the request was made for each OBIS code
|
359
|
+
self.assertEqual(mock_request.call_count, len(ObisCode))
|
360
|
+
|
361
|
+
@patch("requests.request")
|
362
|
+
def test_get_supported_obis_codes_none(self, mock_request):
|
363
|
+
"""Test getting supported OBIS codes when none are supported."""
|
364
|
+
# Set up the mock response to return null unit for all OBIS codes
|
365
|
+
mock_response = MagicMock()
|
366
|
+
mock_response.status_code = 200
|
367
|
+
mock_response.json.return_value = {"unit": None, "aggregatedTimeSeries": []}
|
368
|
+
mock_response.content = json.dumps(mock_response.json.return_value).encode()
|
369
|
+
mock_request.return_value = mock_response
|
370
|
+
|
371
|
+
# Call the method
|
372
|
+
result = self.client.get_supported_obis_codes("INVALID-METERING-POINT")
|
373
|
+
|
374
|
+
# Check the result
|
375
|
+
self.assertIsInstance(result, list)
|
376
|
+
self.assertEqual(len(result), 0) # No supported OBIS codes
|
377
|
+
|
378
|
+
# Check that the request was made for each OBIS code
|
379
|
+
self.assertEqual(mock_request.call_count, len(ObisCode))
|
380
|
+
|
238
381
|
|
239
382
|
if __name__ == "__main__":
|
240
383
|
unittest.main()
|
leneda_client-0.2.0/README.md
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
# Leneda API Client
|
2
|
-
|
3
|
-
[![PyPI version]](https://pypi.org/project/leneda-client/)
|
4
|
-
[![Python versions]](https://pypi.org/project/leneda-client/)
|
5
|
-
[![License]](https://github.com/fedus/leneda-client/blob/main/LICENSE)
|
6
|
-
|
7
|
-
A Python client for interacting with the Leneda energy data platform API.
|
8
|
-
|
9
|
-
## Overview
|
10
|
-
|
11
|
-
This client provides a simple interface to the Leneda API, which allows users to:
|
12
|
-
|
13
|
-
- Retrieve metering data for specific time ranges
|
14
|
-
- Get aggregated metering data (hourly, daily, weekly, monthly, or total)
|
15
|
-
- Create metering data access requests
|
16
|
-
- Use predefined OBIS code constants for easy reference
|
17
|
-
|
18
|
-
## Installation
|
19
|
-
|
20
|
-
```bash
|
21
|
-
pip install leneda-client
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|