leneda-client 0.3.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.3.0/src/leneda_client.egg-info → leneda_client-0.4.0}/PKG-INFO +1 -1
- {leneda_client-0.3.0 → leneda_client-0.4.0}/src/leneda/client.py +44 -16
- {leneda_client-0.3.0 → leneda_client-0.4.0}/src/leneda/exceptions.py +0 -6
- {leneda_client-0.3.0 → leneda_client-0.4.0}/src/leneda/version.py +1 -1
- {leneda_client-0.3.0 → leneda_client-0.4.0/src/leneda_client.egg-info}/PKG-INFO +1 -1
- {leneda_client-0.3.0 → leneda_client-0.4.0}/tests/test_client.py +68 -11
- {leneda_client-0.3.0 → leneda_client-0.4.0}/LICENSE +0 -0
- {leneda_client-0.3.0 → leneda_client-0.4.0}/MANIFEST.in +0 -0
- {leneda_client-0.3.0 → leneda_client-0.4.0}/README.md +0 -0
- {leneda_client-0.3.0 → leneda_client-0.4.0}/examples/advanced_usage.py +0 -0
- {leneda_client-0.3.0 → leneda_client-0.4.0}/examples/basic_usage.py +0 -0
- {leneda_client-0.3.0 → leneda_client-0.4.0}/pyproject.toml +0 -0
- {leneda_client-0.3.0 → leneda_client-0.4.0}/requirements.txt +0 -0
- {leneda_client-0.3.0 → leneda_client-0.4.0}/setup.cfg +0 -0
- {leneda_client-0.3.0 → leneda_client-0.4.0}/setup.py +0 -0
- {leneda_client-0.3.0 → leneda_client-0.4.0}/src/leneda/__init__.py +0 -0
- {leneda_client-0.3.0 → leneda_client-0.4.0}/src/leneda/models.py +0 -0
- {leneda_client-0.3.0 → leneda_client-0.4.0}/src/leneda/obis_codes.py +0 -0
- {leneda_client-0.3.0 → leneda_client-0.4.0}/src/leneda_client.egg-info/SOURCES.txt +0 -0
- {leneda_client-0.3.0 → leneda_client-0.4.0}/src/leneda_client.egg-info/dependency_links.txt +0 -0
- {leneda_client-0.3.0 → leneda_client-0.4.0}/src/leneda_client.egg-info/requires.txt +0 -0
- {leneda_client-0.3.0 → leneda_client-0.4.0}/src/leneda_client.egg-info/top_level.txt +0 -0
- {leneda_client-0.3.0 → leneda_client-0.4.0}/tests/__init__.py +0 -0
@@ -12,7 +12,7 @@ from typing import Any, Dict, List, Optional, Union
|
|
12
12
|
|
13
13
|
import requests
|
14
14
|
|
15
|
-
from .exceptions import ForbiddenException,
|
15
|
+
from .exceptions import ForbiddenException, UnauthorizedException
|
16
16
|
from .models import (
|
17
17
|
AggregatedMeteringData,
|
18
18
|
MeteringData,
|
@@ -250,19 +250,24 @@ class LenedaClient:
|
|
250
250
|
|
251
251
|
return response_data
|
252
252
|
|
253
|
-
def
|
253
|
+
def probe_metering_point_obis_code(self, metering_point_code: str, obis_code: ObisCode) -> bool:
|
254
254
|
"""
|
255
|
-
|
255
|
+
Probe if a metering point provides data for a specific OBIS code.
|
256
256
|
|
257
|
-
This method
|
258
|
-
|
259
|
-
|
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
|
260
264
|
|
261
265
|
Args:
|
262
|
-
metering_point_code: The metering point code to
|
266
|
+
metering_point_code: The metering point code to probe
|
267
|
+
obis_code: The OBIS code to check for data availability
|
263
268
|
|
264
269
|
Returns:
|
265
|
-
bool: True if the metering point
|
270
|
+
bool: True if the metering point provides data for the specified OBIS code, False otherwise
|
266
271
|
|
267
272
|
Raises:
|
268
273
|
UnauthorizedException: If the API returns a 401 status code
|
@@ -273,20 +278,43 @@ class LenedaClient:
|
|
273
278
|
end_date = datetime.now()
|
274
279
|
start_date = end_date - timedelta(weeks=4)
|
275
280
|
|
276
|
-
# Try to get aggregated data for
|
281
|
+
# Try to get aggregated data for the specified OBIS code
|
277
282
|
result = self.get_aggregated_metering_data(
|
278
283
|
metering_point_code=metering_point_code,
|
279
|
-
obis_code=
|
284
|
+
obis_code=obis_code,
|
280
285
|
start_date=start_date,
|
281
286
|
end_date=end_date,
|
282
287
|
aggregation_level="Month",
|
283
288
|
transformation_mode="Accumulation",
|
284
289
|
)
|
285
290
|
|
286
|
-
#
|
287
|
-
|
288
|
-
|
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
|
291
307
|
|
292
|
-
|
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
|
+
]
|
@@ -19,9 +19,3 @@ class ForbiddenException(LenedaException):
|
|
19
19
|
"""Raised when access is forbidden (403 Forbidden), typically due to geoblocking or other access restrictions."""
|
20
20
|
|
21
21
|
pass
|
22
|
-
|
23
|
-
|
24
|
-
class InvalidMeteringPointException(LenedaException):
|
25
|
-
"""Raised when a metering point code is invalid or not accessible."""
|
26
|
-
|
27
|
-
pass
|
@@ -16,7 +16,6 @@ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")
|
|
16
16
|
from src.leneda import LenedaClient
|
17
17
|
from src.leneda.exceptions import (
|
18
18
|
ForbiddenException,
|
19
|
-
InvalidMeteringPointException,
|
20
19
|
UnauthorizedException,
|
21
20
|
)
|
22
21
|
from src.leneda.models import (
|
@@ -283,8 +282,8 @@ class TestLenedaClient(unittest.TestCase):
|
|
283
282
|
)
|
284
283
|
|
285
284
|
@patch("requests.request")
|
286
|
-
def
|
287
|
-
"""Test
|
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."""
|
288
287
|
# Set up the mock response with valid data
|
289
288
|
mock_response = MagicMock()
|
290
289
|
mock_response.status_code = 200
|
@@ -293,7 +292,9 @@ class TestLenedaClient(unittest.TestCase):
|
|
293
292
|
mock_request.return_value = mock_response
|
294
293
|
|
295
294
|
# Call the method
|
296
|
-
result = self.client.
|
295
|
+
result = self.client.probe_metering_point_obis_code(
|
296
|
+
"LU-METERING_POINT1", ObisCode.ELEC_CONSUMPTION_ACTIVE
|
297
|
+
)
|
297
298
|
|
298
299
|
# Check the result
|
299
300
|
self.assertTrue(result)
|
@@ -302,8 +303,8 @@ class TestLenedaClient(unittest.TestCase):
|
|
302
303
|
mock_request.assert_called_once()
|
303
304
|
|
304
305
|
@patch("requests.request")
|
305
|
-
def
|
306
|
-
"""Test
|
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."""
|
307
308
|
# Set up the mock response with null unit
|
308
309
|
mock_response = MagicMock()
|
309
310
|
mock_response.status_code = 200
|
@@ -311,16 +312,72 @@ class TestLenedaClient(unittest.TestCase):
|
|
311
312
|
mock_response.content = json.dumps(mock_response.json.return_value).encode()
|
312
313
|
mock_request.return_value = mock_response
|
313
314
|
|
314
|
-
# Call the method
|
315
|
-
|
316
|
-
|
315
|
+
# Call the method
|
316
|
+
result = self.client.probe_metering_point_obis_code(
|
317
|
+
"INVALID-METERING-POINT", ObisCode.ELEC_CONSUMPTION_ACTIVE
|
318
|
+
)
|
317
319
|
|
318
|
-
# Check the
|
319
|
-
self.
|
320
|
+
# Check the result
|
321
|
+
self.assertFalse(result)
|
320
322
|
|
321
323
|
# Check that the request was made correctly
|
322
324
|
mock_request.assert_called_once()
|
323
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
|
+
|
324
381
|
|
325
382
|
if __name__ == "__main__":
|
326
383
|
unittest.main()
|
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
|
File without changes
|
File without changes
|
File without changes
|