leneda-client 0.4.0__tar.gz → 0.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. {leneda_client-0.4.0/src/leneda_client.egg-info → leneda_client-0.6.0}/PKG-INFO +6 -2
  2. {leneda_client-0.4.0 → leneda_client-0.6.0}/README.md +3 -0
  3. leneda_client-0.6.0/examples/advanced_usage.py +299 -0
  4. leneda_client-0.6.0/examples/basic_usage.py +160 -0
  5. {leneda_client-0.4.0 → leneda_client-0.6.0}/pyproject.toml +3 -3
  6. leneda_client-0.6.0/requirements.txt +3 -0
  7. {leneda_client-0.4.0 → leneda_client-0.6.0}/setup.py +2 -0
  8. {leneda_client-0.4.0 → leneda_client-0.6.0}/src/leneda/client.py +136 -58
  9. {leneda_client-0.4.0 → leneda_client-0.6.0}/src/leneda/exceptions.py +6 -0
  10. {leneda_client-0.4.0 → leneda_client-0.6.0}/src/leneda/models.py +9 -0
  11. leneda_client-0.6.0/src/leneda/py.typed +0 -0
  12. {leneda_client-0.4.0 → leneda_client-0.6.0}/src/leneda/version.py +1 -1
  13. {leneda_client-0.4.0 → leneda_client-0.6.0/src/leneda_client.egg-info}/PKG-INFO +6 -2
  14. {leneda_client-0.4.0 → leneda_client-0.6.0}/src/leneda_client.egg-info/SOURCES.txt +1 -0
  15. leneda_client-0.6.0/src/leneda_client.egg-info/requires.txt +3 -0
  16. leneda_client-0.6.0/tests/test_client.py +475 -0
  17. leneda_client-0.4.0/examples/advanced_usage.py +0 -540
  18. leneda_client-0.4.0/examples/basic_usage.py +0 -163
  19. leneda_client-0.4.0/requirements.txt +0 -2
  20. leneda_client-0.4.0/src/leneda_client.egg-info/requires.txt +0 -2
  21. leneda_client-0.4.0/tests/test_client.py +0 -383
  22. {leneda_client-0.4.0 → leneda_client-0.6.0}/LICENSE +0 -0
  23. {leneda_client-0.4.0 → leneda_client-0.6.0}/MANIFEST.in +0 -0
  24. {leneda_client-0.4.0 → leneda_client-0.6.0}/setup.cfg +0 -0
  25. {leneda_client-0.4.0 → leneda_client-0.6.0}/src/leneda/__init__.py +0 -0
  26. {leneda_client-0.4.0 → leneda_client-0.6.0}/src/leneda/obis_codes.py +0 -0
  27. {leneda_client-0.4.0 → leneda_client-0.6.0}/src/leneda_client.egg-info/dependency_links.txt +0 -0
  28. {leneda_client-0.4.0 → leneda_client-0.6.0}/src/leneda_client.egg-info/top_level.txt +0 -0
  29. {leneda_client-0.4.0 → leneda_client-0.6.0}/tests/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: leneda-client
3
- Version: 0.4.0
3
+ Version: 0.6.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
@@ -22,8 +22,9 @@ Classifier: Topic :: Utilities
22
22
  Requires-Python: >=3.8
23
23
  Description-Content-Type: text/markdown
24
24
  License-File: LICENSE
25
- Requires-Dist: requests>=2.25.0
25
+ Requires-Dist: aiohttp>=3.9.0
26
26
  Requires-Dist: python-dateutil>=2.8.2
27
+ Requires-Dist: pytest-asyncio>=0.23.0
27
28
  Dynamic: author
28
29
  Dynamic: author-email
29
30
  Dynamic: classifier
@@ -46,6 +47,9 @@ Dynamic: summary
46
47
 
47
48
  A Python client for interacting with the Leneda energy data platform API.
48
49
 
50
+ PLEASE NOTE: As long as the library is in a version below 1.0.0, breaking changes
51
+ may also be introduced between minor version bumps.
52
+
49
53
  ## Overview
50
54
 
51
55
  This client provides a simple interface to the Leneda API, which allows users to:
@@ -7,6 +7,9 @@
7
7
 
8
8
  A Python client for interacting with the Leneda energy data platform API.
9
9
 
10
+ PLEASE NOTE: As long as the library is in a version below 1.0.0, breaking changes
11
+ may also be introduced between minor version bumps.
12
+
10
13
  ## Overview
11
14
 
12
15
  This client provides a simple interface to the Leneda API, which allows users to:
@@ -0,0 +1,299 @@
1
+ """
2
+ Advanced usage examples for the Leneda API client.
3
+
4
+ This script demonstrates more complex use cases of the Leneda API client,
5
+ including data analysis, visualization, and error handling.
6
+ It accepts API credentials via command-line arguments or environment variables.
7
+
8
+ Environment variables:
9
+ LENEDA_API_KEY: Your Leneda API key
10
+ LENEDA_ENERGY_ID: Your Energy ID
11
+
12
+ Usage:
13
+ python advanced_usage.py --api-key YOUR_API_KEY --energy-id YOUR_ENERGY_ID --metering-point LU-METERING_POINT1
14
+ python advanced_usage.py --api-key YOUR_API_KEY --energy-id YOUR_ENERGY_ID --metering-point LU-METERING_POINT1 --example 2
15
+ """
16
+
17
+ import argparse
18
+ import asyncio
19
+ import logging
20
+ import os
21
+ import sys
22
+ from datetime import datetime, timedelta
23
+ from typing import Optional
24
+
25
+ import matplotlib.pyplot as plt
26
+ import pandas as pd
27
+
28
+ from leneda import LenedaClient
29
+ from leneda.models import MeteringData
30
+ from leneda.obis_codes import ObisCode
31
+
32
+ # Set up logging
33
+ logging.basicConfig(
34
+ level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
35
+ )
36
+ logger = logging.getLogger("leneda_advanced_example")
37
+
38
+
39
+ def parse_arguments() -> argparse.Namespace:
40
+ """Parse command-line arguments."""
41
+ parser = argparse.ArgumentParser(description="Leneda API Client Advanced Usage Example")
42
+
43
+ # API credentials
44
+ parser.add_argument(
45
+ "--api-key",
46
+ help="Your Leneda API key (or set LENEDA_API_KEY environment variable)",
47
+ )
48
+ parser.add_argument(
49
+ "--energy-id",
50
+ help="Your Energy ID (or set LENEDA_ENERGY_ID environment variable)",
51
+ )
52
+
53
+ # Other parameters
54
+ parser.add_argument(
55
+ "--metering-point",
56
+ help="Metering point code (default: LU-METERING_POINT1)",
57
+ )
58
+ parser.add_argument(
59
+ "--example",
60
+ type=int,
61
+ default=0,
62
+ help="Example number to run (0 for all, 1-3 for specific examples)",
63
+ )
64
+ parser.add_argument(
65
+ "--days",
66
+ type=int,
67
+ default=7,
68
+ help="Number of days to retrieve data for (default: 7)",
69
+ )
70
+ parser.add_argument(
71
+ "--year",
72
+ type=int,
73
+ default=None,
74
+ help="Year to analyze (default: current year)",
75
+ )
76
+ parser.add_argument(
77
+ "--threshold",
78
+ type=float,
79
+ default=0.0,
80
+ help="Threshold for anomaly detection (default: 0.0)",
81
+ )
82
+ parser.add_argument(
83
+ "--save-plots",
84
+ action="store_true",
85
+ help="Save plots to files instead of displaying them",
86
+ )
87
+ parser.add_argument(
88
+ "--output-dir",
89
+ default="plots",
90
+ help="Directory to save plots (default: plots)",
91
+ )
92
+ parser.add_argument("--debug", action="store_true", help="Enable debug logging")
93
+
94
+ return parser.parse_args()
95
+
96
+
97
+ def get_credentials(args: argparse.Namespace) -> tuple[str, str]:
98
+ """Get API credentials from arguments or environment variables."""
99
+ api_key = args.api_key or os.environ.get("LENEDA_API_KEY")
100
+ energy_id = args.energy_id or os.environ.get("LENEDA_ENERGY_ID")
101
+
102
+ if not api_key:
103
+ logger.error(
104
+ "API key not provided. Use --api-key or set LENEDA_API_KEY environment variable."
105
+ )
106
+ sys.exit(1)
107
+
108
+ if not energy_id:
109
+ logger.error(
110
+ "Energy ID not provided. Use --energy-id or set LENEDA_ENERGY_ID environment variable."
111
+ )
112
+ sys.exit(1)
113
+
114
+ return api_key, energy_id
115
+
116
+
117
+ def convert_to_dataframe(data: MeteringData) -> pd.DataFrame:
118
+ """Convert MeteringData to a pandas DataFrame."""
119
+ df = pd.DataFrame(
120
+ [
121
+ {
122
+ "timestamp": item.started_at,
123
+ "value": item.value,
124
+ "type": item.type,
125
+ "calculated": item.calculated,
126
+ }
127
+ for item in data.items
128
+ ]
129
+ )
130
+ df.set_index("timestamp", inplace=True)
131
+ return df
132
+
133
+
134
+ def plot_consumption_data(
135
+ df: pd.DataFrame, title: str, unit: str, save_path: Optional[str] = None
136
+ ) -> None:
137
+ """Plot consumption data."""
138
+ plt.figure(figsize=(12, 6))
139
+ plt.plot(df.index, df["value"], label="Consumption")
140
+ plt.title(title)
141
+ plt.xlabel("Time")
142
+ plt.ylabel(f"Consumption ({unit})")
143
+ plt.grid(True)
144
+ plt.legend()
145
+
146
+ if save_path:
147
+ os.makedirs(os.path.dirname(save_path), exist_ok=True)
148
+ plt.savefig(save_path)
149
+ plt.close()
150
+ else:
151
+ plt.show()
152
+
153
+
154
+ def detect_anomalies(df: pd.DataFrame, threshold: float) -> pd.DataFrame:
155
+ """Detect anomalies in the data using a simple threshold-based approach."""
156
+ mean = df["value"].mean()
157
+ std = df["value"].std()
158
+ df["anomaly"] = abs(df["value"] - mean) > (threshold * std)
159
+ return df
160
+
161
+
162
+ async def main() -> None:
163
+ # Parse command-line arguments
164
+ args = parse_arguments()
165
+
166
+ # Set up debug logging if requested
167
+ if args.debug:
168
+ logging.getLogger("leneda").setLevel(logging.DEBUG)
169
+ logger.setLevel(logging.DEBUG)
170
+ logger.debug("Debug logging enabled")
171
+
172
+ # Get API credentials
173
+ api_key, energy_id = get_credentials(args)
174
+
175
+ # Get other parameters
176
+ metering_point = args.metering_point
177
+ example_num = args.example
178
+ days = args.days
179
+ threshold = args.threshold
180
+ save_plots = args.save_plots
181
+ output_dir = args.output_dir
182
+
183
+ # Initialize the client
184
+ client = LenedaClient(api_key, energy_id, debug=args.debug)
185
+
186
+ # Run all examples or a specific one based on the command-line argument
187
+ if example_num == 0 or example_num == 1:
188
+ # Example 1: Get and visualize hourly electricity consumption for the last week
189
+ end_date = datetime.now()
190
+ start_date = end_date - timedelta(days=days)
191
+
192
+ print(
193
+ f"\nExample 1: Visualizing hourly electricity consumption (kWh) for the last {days} days"
194
+ )
195
+ consumption_data = await client.get_metering_data(
196
+ metering_point_code=metering_point,
197
+ obis_code=ObisCode.ELEC_CONSUMPTION_ACTIVE,
198
+ start_date_time=start_date,
199
+ end_date_time=end_date,
200
+ )
201
+
202
+ # Convert to DataFrame and compute kWh for each 15-min period
203
+ df = convert_to_dataframe(consumption_data)
204
+ df["kWh"] = df["value"] * 0.25 # 15 min = 0.25 h
205
+ # Resample to hourly energy (sum of 4 periods per hour)
206
+ hourly_kwh = df["kWh"].resample("H").sum()
207
+ plt.figure(figsize=(12, 6))
208
+ plt.plot(hourly_kwh.index, hourly_kwh.values, label="Hourly Energy Consumption")
209
+ plt.title(
210
+ f"Hourly Electricity Consumption (kWh) ({start_date.strftime('%Y-%m-%d')} to {end_date.strftime('%Y-%m-%d')})"
211
+ )
212
+ plt.xlabel("Time")
213
+ plt.ylabel("Consumption (kWh)")
214
+ plt.grid(True)
215
+ plt.legend()
216
+ if save_plots:
217
+ os.makedirs(output_dir, exist_ok=True)
218
+ plt.savefig(os.path.join(output_dir, "hourly_consumption_kwh.png"))
219
+ plt.close()
220
+ else:
221
+ plt.show()
222
+
223
+ if example_num == 0 or example_num == 2:
224
+ # Example 2: Analyze daily consumption patterns (average power in kW)
225
+ end_date = datetime.now()
226
+ start_date = end_date - timedelta(days=30) # Last 30 days
227
+
228
+ print("\nExample 2: Analyzing daily average power patterns (kW)")
229
+ consumption_data = await client.get_metering_data(
230
+ metering_point_code=metering_point,
231
+ obis_code=ObisCode.ELEC_CONSUMPTION_ACTIVE,
232
+ start_date_time=start_date,
233
+ end_date_time=end_date,
234
+ )
235
+
236
+ # Convert to DataFrame and analyze
237
+ df = convert_to_dataframe(consumption_data)
238
+ df["hour"] = df.index.hour
239
+ df["day_of_week"] = df.index.day_name()
240
+
241
+ # Calculate average power by hour (kW)
242
+ hourly_avg = df.groupby("hour")["value"].mean()
243
+ plt.figure(figsize=(12, 6))
244
+ hourly_avg.plot(kind="bar")
245
+ plt.title("Average Hourly Power (kW)")
246
+ plt.xlabel("Hour of Day")
247
+ plt.ylabel("Average Power (kW)")
248
+ plt.grid(True)
249
+ if save_plots:
250
+ os.makedirs(output_dir, exist_ok=True)
251
+ plt.savefig(os.path.join(output_dir, "hourly_patterns_kw.png"))
252
+ plt.close()
253
+ else:
254
+ plt.show()
255
+
256
+ if example_num == 0 or example_num == 3:
257
+ # Example 3: Detect anomalies in consumption data
258
+ end_date = datetime.now()
259
+ start_date = end_date - timedelta(days=90) # Last 90 days
260
+
261
+ print("\nExample 3: Detecting anomalies in consumption data")
262
+ consumption_data = await client.get_metering_data(
263
+ metering_point_code=metering_point,
264
+ obis_code=ObisCode.ELEC_CONSUMPTION_ACTIVE,
265
+ start_date_time=start_date,
266
+ end_date_time=end_date,
267
+ )
268
+
269
+ # Convert to DataFrame and detect anomalies
270
+ df = convert_to_dataframe(consumption_data)
271
+ df = detect_anomalies(df, threshold)
272
+
273
+ # Plot the data with anomalies highlighted
274
+ plt.figure(figsize=(12, 6))
275
+ plt.plot(df.index, df["value"], label="Consumption")
276
+ anomalies = df[df["anomaly"]]
277
+ plt.scatter(
278
+ anomalies.index,
279
+ anomalies["value"],
280
+ color="red",
281
+ label="Anomalies",
282
+ zorder=5,
283
+ )
284
+ plt.title(f"Electricity Consumption with Anomalies (Threshold: {threshold}σ)")
285
+ plt.xlabel("Time")
286
+ plt.ylabel(f"Consumption ({consumption_data.unit})")
287
+ plt.grid(True)
288
+ plt.legend()
289
+
290
+ if save_plots:
291
+ os.makedirs(output_dir, exist_ok=True)
292
+ plt.savefig(os.path.join(output_dir, "anomalies.png"))
293
+ plt.close()
294
+ else:
295
+ plt.show()
296
+
297
+
298
+ if __name__ == "__main__":
299
+ asyncio.run(main())
@@ -0,0 +1,160 @@
1
+ """
2
+ Basic usage examples for the Leneda API client.
3
+
4
+ This script demonstrates basic usage of the Leneda API client.
5
+ It accepts API credentials via command-line arguments or environment variables.
6
+
7
+ Environment variables:
8
+ LENEDA_API_KEY: Your Leneda API key
9
+ LENEDA_ENERGY_ID: Your Energy ID
10
+
11
+ Usage:
12
+ python basic_usage.py --api-key YOUR_API_KEY --energy-id YOUR_ENERGY_ID --metering-point LU-METERING_POINT1
13
+ """
14
+
15
+ import argparse
16
+ import asyncio
17
+ import logging
18
+ import os
19
+ import sys
20
+ from datetime import datetime, timedelta
21
+
22
+ from leneda import LenedaClient
23
+ from leneda.obis_codes import ObisCode
24
+
25
+ # Set up logging
26
+ logging.basicConfig(
27
+ level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
28
+ )
29
+ logger = logging.getLogger("leneda_example")
30
+
31
+
32
+ def parse_arguments() -> argparse.Namespace:
33
+ """Parse command-line arguments."""
34
+ parser = argparse.ArgumentParser(description="Leneda API Client Basic Usage Example")
35
+
36
+ # API credentials
37
+ parser.add_argument(
38
+ "--api-key",
39
+ help="Your Leneda API key (or set LENEDA_API_KEY environment variable)",
40
+ )
41
+ parser.add_argument(
42
+ "--energy-id",
43
+ help="Your Energy ID (or set LENEDA_ENERGY_ID environment variable)",
44
+ )
45
+
46
+ # Other parameters
47
+ parser.add_argument(
48
+ "--metering-point",
49
+ help="Metering point code (default: LU-METERING_POINT1)",
50
+ )
51
+ parser.add_argument(
52
+ "--days",
53
+ type=int,
54
+ default=7,
55
+ help="Number of days to retrieve data for (default: 7)",
56
+ )
57
+ parser.add_argument("--debug", action="store_true", help="Enable debug logging")
58
+
59
+ return parser.parse_args()
60
+
61
+
62
+ def get_credentials(args: argparse.Namespace) -> tuple[str, str]:
63
+ """Get API credentials from arguments or environment variables."""
64
+ api_key = args.api_key or os.environ.get("LENEDA_API_KEY")
65
+ energy_id = args.energy_id or os.environ.get("LENEDA_ENERGY_ID")
66
+
67
+ if not api_key:
68
+ logger.error(
69
+ "API key not provided. Use --api-key or set LENEDA_API_KEY environment variable."
70
+ )
71
+ sys.exit(1)
72
+
73
+ if not energy_id:
74
+ logger.error(
75
+ "Energy ID not provided. Use --energy-id or set LENEDA_ENERGY_ID environment variable."
76
+ )
77
+ sys.exit(1)
78
+
79
+ return api_key, energy_id
80
+
81
+
82
+ async def main() -> None:
83
+ # Parse command-line arguments
84
+ args = parse_arguments()
85
+
86
+ # Set up debug logging if requested
87
+ if args.debug:
88
+ logging.getLogger("leneda").setLevel(logging.DEBUG)
89
+ logger.setLevel(logging.DEBUG)
90
+ logger.debug("Debug logging enabled")
91
+
92
+ # Get API credentials
93
+ api_key, energy_id = get_credentials(args)
94
+
95
+ # Get other parameters
96
+ metering_point = args.metering_point
97
+ days = args.days
98
+
99
+ # Initialize the client
100
+ client = LenedaClient(api_key, energy_id, debug=args.debug)
101
+
102
+ # Example 1: Get hourly electricity consumption data for the specified number of days
103
+ end_date = datetime.now()
104
+ start_date = end_date - timedelta(days=days)
105
+
106
+ print(f"\nExample 1: Getting hourly electricity consumption data for the last {days} days")
107
+ consumption_data = await client.get_metering_data(
108
+ metering_point_code=metering_point,
109
+ obis_code=ObisCode.ELEC_CONSUMPTION_ACTIVE,
110
+ start_date_time=start_date.strftime("%Y-%m-%dT%H:%M:%SZ"),
111
+ end_date_time=end_date.strftime("%Y-%m-%dT%H:%M:%SZ"),
112
+ )
113
+
114
+ # Process and display the data
115
+ print(f"Retrieved {len(consumption_data.items)} consumption measurements")
116
+ print(f"Unit: {consumption_data.unit}")
117
+ print(f"Interval length: {consumption_data.interval_length}")
118
+ print(f"Metering point: {consumption_data.metering_point_code}")
119
+ print(f"OBIS code: {consumption_data.obis_code}")
120
+
121
+ # Display the first 3 items
122
+ if consumption_data.items:
123
+ print("\nFirst 3 measurements:")
124
+ for item in consumption_data.items[:3]:
125
+ print(
126
+ f"Time: {item.started_at.isoformat()}, Value: {item.value} {consumption_data.unit}, "
127
+ f"Type: {item.type}, Version: {item.version}, Calculated: {item.calculated}"
128
+ )
129
+
130
+ # Example 2: Get monthly aggregated electricity consumption for the current year
131
+ today = datetime.now()
132
+ start_of_year = datetime(today.year, 1, 1)
133
+
134
+ print("\nExample 2: Getting monthly aggregated electricity consumption for the current year")
135
+ aggregated_data = await client.get_aggregated_metering_data(
136
+ metering_point_code=metering_point,
137
+ obis_code=ObisCode.ELEC_CONSUMPTION_ACTIVE,
138
+ start_date=start_of_year.strftime("%Y-%m-%d"),
139
+ end_date=today.strftime("%Y-%m-%d"),
140
+ aggregation_level="Month",
141
+ transformation_mode="Accumulation",
142
+ )
143
+
144
+ # Process and display the data
145
+ print(f"Retrieved {len(aggregated_data.aggregated_time_series)} monthly measurements")
146
+ print(f"Unit: {aggregated_data.unit}")
147
+
148
+ # Display all measurements
149
+ if aggregated_data.aggregated_time_series:
150
+ print("\nMonthly measurements:")
151
+ for metering_value in aggregated_data.aggregated_time_series:
152
+ print(
153
+ f"Period: {metering_value.started_at.strftime('%Y-%m')}, "
154
+ f"Value: {metering_value.value} {aggregated_data.unit}, "
155
+ f"Calculated: {metering_value.calculated}"
156
+ )
157
+
158
+
159
+ if __name__ == "__main__":
160
+ asyncio.run(main())
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [tool.black]
6
6
  line-length = 100
7
- target-version = ['py38', 'py39', 'py310', 'py311']
7
+ target-version = ['py39', 'py310', 'py311']
8
8
  include = '\.pyi?$'
9
9
 
10
10
  [tool.isort]
@@ -14,12 +14,12 @@ multi_line_output = 3
14
14
  src_paths = ["src", "tests"]
15
15
 
16
16
  [tool.mypy]
17
- python_version = "3.8"
17
+ python_version = "3.9"
18
18
  warn_return_any = true
19
19
  warn_unused_configs = true
20
20
  disallow_untyped_defs = true
21
21
  disallow_incomplete_defs = true
22
- mypy_path = "src"
22
+ explicit_package_bases = true
23
23
 
24
24
  [tool.pytest.ini_options]
25
25
  testpaths = ["tests"]
@@ -0,0 +1,3 @@
1
+ aiohttp>=3.9.0
2
+ python-dateutil>=2.8.2
3
+ pytest-asyncio>=0.23.0
@@ -35,6 +35,8 @@ setup(
35
35
  url="https://github.com/fedus/leneda-client",
36
36
  package_dir={"": "src"},
37
37
  packages=find_packages(where="src"),
38
+ include_package_data=True,
39
+ package_data={"leneda": ["py.typed"]},
38
40
  install_requires=requirements,
39
41
  classifiers=[
40
42
  "Development Status :: 4 - Beta",