solaredge 2.0.0__tar.gz → 3.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.
@@ -0,0 +1,367 @@
1
+ Metadata-Version: 2.3
2
+ Name: solaredge
3
+ Version: 3.1.0
4
+ Summary: Sync and async Python client for the SolarEdge Monitoring API (V2)
5
+ Keywords: solaredge,solar,monitoring,inverter,api,httpx
6
+ Author: evworth
7
+ Author-email: evworth <elliotworth@protonmail.com>
8
+ License: MIT
9
+ Classifier: Development Status :: 5 - Production/Stable
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Framework :: AsyncIO
20
+ Classifier: Typing :: Typed
21
+ Requires-Dist: httpx>=0.28.1
22
+ Requires-Python: >=3.10
23
+ Project-URL: Homepage, https://github.com/EVWorth/solaredge
24
+ Project-URL: Issues, https://github.com/EVWorth/solaredge/issues
25
+ Project-URL: Changelog, https://github.com/EVWorth/solaredge/blob/main/CHANGELOG.md
26
+ Project-URL: SolarEdge API docs, https://api-docs.solaredge.com/
27
+ Description-Content-Type: text/markdown
28
+
29
+ # SolarEdge Client (Monitoring API)
30
+
31
+ <p align="center">
32
+ <a href="https://docs.astral.sh/uv/">
33
+ <img src="https://img.shields.io/badge/packaging-UV-299bd7?style=flat-square&logo=data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDEiIGhlaWdodD0iNDEiIHZpZXdCb3g9IjAgMCA0MSA0MSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTS01LjI4NjE5ZS0wNiAwLjE2ODYyOUwwLjA4NDMwOTggMjAuMTY4NUwwLjE1MTc2MiAzNi4xNjgzQzAuMTYxMDc1IDM4LjM3NzQgMS45NTk0NyA0MC4xNjA3IDQuMTY4NTkgNDAuMTUxNEwyMC4xNjg0IDQwLjA4NEwzMC4xNjg0IDQwLjA0MThMMzEuMTg1MiA0MC4wMzc1QzMzLjM4NzcgNDAuMDI4MiAzNS4xNjgzIDM4LjIwMjYgMzUuMTY4MyAzNlYzNkwzNy4wMDAzIDM2TDM3LjAwMDMgMzkuOTk5Mkw0MC4xNjgzIDM5Ljk5OTZMMzkuOTk5NiAtOS45NDY1M2UtMDdMMjEuNTk5OCAwLjA3NzU2ODlMMjEuNjc3NCAxNi4wMTg1TDIxLjY3NzQgMjUuOTk5OEwyMC4wNzc0IDI1Ljk5OThMMTguMzk5OCAyNS45OTk4TDE4LjQ3NzQgMTYuMDMyTDE4LjM5OTggMC4wOTEwNTkzTC01LjI4NjE5ZS0wNiAwLjE2ODYyOVoiIGZpbGw9IiNERTVGRTkiLz4KPC9zdmc+Cg==" alt="UV">
34
+ </a>
35
+ <a href="https://docs.astral.sh/ruff/">
36
+ <img src="https://img.shields.io/badge/code%20style-Ruff-8400ff?style=flat-square" alt="Ruff">
37
+ </a>
38
+ <a href="https://github.com/j178/prek">
39
+ <img src="https://img.shields.io/badge/hooks-prek-f6d55c?style=flat-square" alt="prek">
40
+ </a>
41
+ </p>
42
+ <p align="center">
43
+ <a href="https://pypi.org/project/solaredge/">
44
+ <img src="https://img.shields.io/pypi/v/solaredge?logo=python&logoColor=fff&style=flat-square" alt="PyPI Version">
45
+ </a>
46
+ <img src="https://img.shields.io/pypi/pyversions/solaredge?style=flat-square&logo=python&logoColor=fff" alt="Supported Python versions">
47
+ <img src="https://img.shields.io/pypi/l/solaredge?style=flat-square" alt="License">
48
+ </p>
49
+
50
+
51
+
52
+ A Python client for the SolarEdge Monitoring API (V2), with synchronous and asynchronous interfaces and a method for every documented operation.
53
+
54
+ > **Upgrading from 2.x?** SolarEdge retired Monitoring API V1 on November 3,
55
+ > 2026, and every request from 2.x now fails with HTTP 410. 3.0 talks to V2,
56
+ > which needs a new API key and has different endpoints and response shapes.
57
+ > See [Migrating from 2.x](#migrating-from-2x).
58
+
59
+ ## Table of Contents
60
+
61
+ - [Features](#features)
62
+ - [Installation](#installation)
63
+ - [Credentials](#credentials)
64
+ - [Quick Start](#quick-start)
65
+ - [Synchronous Usage](#synchronous-usage)
66
+ - [Asynchronous Usage](#asynchronous-usage)
67
+ - [Methods](#methods)
68
+ - [Error Handling](#error-handling)
69
+ - [Rate Limits](#rate-limits)
70
+ - [Migrating from 2.x](#migrating-from-2x)
71
+ - [Development](#development)
72
+ - [API Documentation](#api-documentation)
73
+
74
+ ## Features
75
+
76
+ - **Complete V2 coverage**: all 56 operations in SolarEdge's Basic Monitoring
77
+ (2.1.0) and Advanced Monitoring (2.0.1) APIs, plus `get()` for anything added later
78
+ - **Sync & Async Support**: `MonitoringClient` and `AsyncMonitoringClient` share
79
+ one definition of every method, so they cannot drift apart
80
+ - **Typed**: full annotations, `py.typed`, and `Literal` types for every enumerated parameter
81
+ - **OAuth built in**: Site Access tokens are refreshed automatically, with a
82
+ `python -m solaredge login` helper for homeowners
83
+ - **Safe by default**: arguments are validated before anything is sent, and
84
+ credentials never appear in URLs, reprs or error messages
85
+
86
+ ## Installation
87
+
88
+ ```bash
89
+ pip install solaredge
90
+ poetry add solaredge
91
+ uv add solaredge
92
+ ```
93
+
94
+ ## Credentials
95
+
96
+ V1 API keys do not work with V2. Which V2 credential you need depends on who
97
+ you are:
98
+
99
+ | You are | Access type | Pass to the client |
100
+ | --- | --- | --- |
101
+ | A **homeowner** reading your own system | Site Access (OAuth 2.0) | `oauth=OAuth2(...)`, or `access_token="..."` |
102
+ | An **installer** with a fleet of sites | Fleet Access (API key) | `api_key="..."` |
103
+
104
+ SolarEdge only offers Fleet Access keys to installer accounts, so homeowners
105
+ use OAuth. See SolarEdge's
106
+ [Homeowner and Self-Access Guide](https://api-docs.solaredge.com/docs/developer-platform/df8kx5j4z9vi0-homeowner-and-self-access-guide).
107
+
108
+ ### Homeowners: Site Access (OAuth)
109
+
110
+ 1. **Create a developer account:** sign in to the
111
+ [Developer Platform](https://developer.solaredge.com/) with your
112
+ mySolarEdge login and fill in the developer profile. The Free tier is enough.
113
+ 2. **Create a Site Access application:**
114
+ - Service: Monitoring API.
115
+ - Redirect URI: `http://localhost:8080/oauth/callback`.
116
+ - Scopes: `SITE_DATA` and `DEVICE_DATA`.
117
+ - Copy the client secret: it is shown only once.
118
+ 3. **Authorize the app for your site** with the bundled helper. It opens the
119
+ consent page, catches the redirect and saves the token to a file only you
120
+ can read:
121
+
122
+ ```bash
123
+ export SOLAREDGE_CLIENT_SECRET=... # or omit it to be prompted
124
+ python -m solaredge login --client-id YOUR_CLIENT_ID
125
+ # Saved the token for site 1234567 to solaredge-token.json (mode 600).
126
+ ```
127
+
128
+ 4. **Use the token.** Access tokens last 2 hours; `OAuth2` refreshes them
129
+ automatically. Each refresh also **replaces the refresh token**, so save
130
+ every new token in `on_token_update`, or the next run will hold a dead one.
131
+ A refresh token unused for 14 days expires, and you have to run
132
+ `login` again.
133
+
134
+ ```python
135
+ import json
136
+ from pathlib import Path
137
+
138
+ from solaredge import MonitoringClient, OAuth2, OAuthToken
139
+
140
+ TOKEN_FILE = Path("solaredge-token.json")
141
+
142
+
143
+ def save(token: OAuthToken) -> None:
144
+ TOKEN_FILE.write_text(json.dumps(token.to_dict()))
145
+
146
+
147
+ auth = OAuth2(
148
+ OAuthToken.from_dict(json.loads(TOKEN_FILE.read_text())),
149
+ client_id="YOUR_CLIENT_ID",
150
+ client_secret="YOUR_CLIENT_SECRET",
151
+ on_token_update=save,
152
+ )
153
+
154
+ with MonitoringClient(oauth=auth) as client:
155
+ overview = client.get_overview(auth.token.site_id)
156
+ ```
157
+
158
+ `OAuth2` works with both clients. With `AsyncMonitoringClient`,
159
+ `on_token_update` may be an `async` function. Concurrent requests share a single
160
+ refresh, so they never spend the same refresh token twice.
161
+
162
+ If you already manage tokens yourself, for example through Home Assistant's
163
+ OAuth helper, pass the current one with `MonitoringClient(access_token="...")`.
164
+
165
+ Building an app for *other* homeowners? `solaredge.oauth` has the pieces:
166
+ `authorization_url`, `parse_callback`, `exchange_code`, `refresh_token`,
167
+ `revoke_token`, and their `async_` variants.
168
+
169
+ ### Installers: Fleet Access (API key)
170
+
171
+ Create a Fleet Access application on the Developer Platform and pass its key:
172
+ `MonitoringClient("YOUR_API_KEY")`. It is sent in the `X-API-Key` header, and
173
+ it is the only credential accepted by `get_sites` and `get_alerts`, which list
174
+ data across a fleet.
175
+
176
+ ## Quick Start
177
+
178
+ All time windows take **timezone-aware** `datetime`s, which are sent as UTC. V2
179
+ rejects timestamps without an offset, so a naive `datetime` raises
180
+ `SolarEdgeValidationError` before any request is sent. Where `start`/`end` are
181
+ optional, leaving them out means "midnight today until now" in the site's
182
+ time zone.
183
+
184
+ The examples below use `auth`, the `OAuth2` object from
185
+ [Credentials](#homeowners-site-access-oauth). With a Fleet Access key, pass
186
+ `api_key="..."` instead.
187
+
188
+ ### Synchronous Usage
189
+
190
+ ```python
191
+ from datetime import datetime, timedelta, timezone
192
+
193
+ from solaredge import MonitoringClient
194
+
195
+ SITE_ID = 1234567
196
+ now = datetime.now(timezone.utc)
197
+
198
+ with MonitoringClient(oauth=auth) as client:
199
+ details = client.get_site_details(SITE_ID)
200
+ today = client.get_overview(SITE_ID)
201
+ daily = client.get_energy(
202
+ SITE_ID,
203
+ start=now - timedelta(days=30),
204
+ end=now,
205
+ resolution="DAY",
206
+ unit="KWH",
207
+ )
208
+ batteries = client.get_storage_telemetry(SITE_ID)
209
+ ```
210
+
211
+ ### Asynchronous Usage
212
+
213
+ ```python
214
+ import asyncio
215
+ from datetime import datetime, timedelta, timezone
216
+
217
+ from solaredge import AsyncMonitoringClient
218
+
219
+
220
+ async def main():
221
+ now = datetime.now(timezone.utc)
222
+ async with AsyncMonitoringClient(oauth=auth) as client:
223
+ details, power = await asyncio.gather(
224
+ client.get_site_details(1234567),
225
+ client.get_power(1234567, start=now - timedelta(hours=2), end=now),
226
+ )
227
+
228
+
229
+ asyncio.run(main())
230
+ ```
231
+
232
+ ### Methods
233
+
234
+ Every method returns the parsed JSON unchanged. Optional arguments left as
235
+ `None` are not sent, so the API's own defaults apply. The
236
+ [API reference](docs/SE_monitoring_API.md) lists the paths, parameters and
237
+ per-resolution window limits.
238
+
239
+ | Area | Methods |
240
+ | --- | --- |
241
+ | Sites | `get_sites`, `get_site_details`, `get_overview`, `get_performance`, `get_power`, `get_energy`, `get_environmental_benefits`, `get_lifetime_energy`, `get_external_production_energy` |
242
+ | Alerts | `get_alerts` (all sites), `get_site_alerts` |
243
+ | Inventory | `get_devices`, `get_device` |
244
+ | Telemetry | `get_inverters_telemetry`, `get_meters_telemetry`, `get_storage_telemetry` |
245
+ | Status | `get_inverters_status`, `get_storage_status` |
246
+ | Lifetime counters | `get_inverters_lifetime_energy`, `get_meters_lifetime_energy`, `get_storage_lifetime_energy` |
247
+ | Inverter | `get_inverter_power`, `_energy`, `_consumed_ac_power`, `_consumed_ac_energy`, `_voltage`, `_current`, `_frequency` |
248
+ | Meter | `get_meter_production_power` / `_energy`, `get_meter_consumption_*`, `get_meter_import_*`, `get_meter_export_*` |
249
+ | Battery | `get_battery_charge_power`, `_discharge_power`, `_charge_energy`, `_discharge_energy`, `_state_of_energy`, `_remaining_energy` |
250
+ | Advanced Monitoring* | `get_power_per_destination`, `get_energy_per_destination`, `get_current_power_flow`, `start_live_power_flow`, `get_weather_guard`, `get_backup_reserve` |
251
+ | Anything else | `get(path, params=None)` |
252
+
253
+ \* Business Pro and Enterprise plans only. Other plans get HTTP 403, raised as
254
+ `SolarEdgeAuthError`.
255
+
256
+ Telemetry, status and lifetime methods cover every device of that kind on the
257
+ site. Pass `serial_numbers=[...]` to limit them to specific devices. V2 reports
258
+ units in upper case (`"WH"`, `"KWH"`, `"W"`), so compare them
259
+ case-insensitively.
260
+
261
+ ### Error Handling
262
+
263
+ Every failure raises a `SolarEdgeError` subclass, so you never need to import
264
+ `httpx` to handle one. The originating `httpx` exception is preserved as
265
+ `__cause__` if you want the transport-level detail.
266
+
267
+ ```python
268
+ from solaredge import (
269
+ MonitoringClient,
270
+ SolarEdgeAuthError,
271
+ SolarEdgeRateLimitError,
272
+ SolarEdgeError,
273
+ )
274
+
275
+ with MonitoringClient("YOUR_API_KEY") as client:
276
+ try:
277
+ flow = client.get_current_power_flow(1234567)
278
+ except SolarEdgeAuthError:
279
+ ... # bad credentials, or an operation your plan does not include
280
+ except SolarEdgeRateLimitError as exc:
281
+ ... # exc.retry_after, exc.limits
282
+ except SolarEdgeError:
283
+ ... # anything else this library raises
284
+ ```
285
+
286
+ The hierarchy:
287
+
288
+ | Exception | Raised when |
289
+ | --- | --- |
290
+ | `SolarEdgeError` | Base class for everything below |
291
+ | `SolarEdgeValidationError` | Arguments rejected locally, before any request. Also a `ValueError` |
292
+ | `SolarEdgeAPIError` | The API returned an error status, e.g. 400 for a window that is too long |
293
+ | `SolarEdgeAuthError` | 401 / 403: credentials invalid or expired, missing scope, or the operation is not in your plan |
294
+ | `SolarEdgeOAuthError` | The token endpoint rejected a code or refresh token. Carries `error` and `error_description`; an expired grant means running `login` again |
295
+ | `SolarEdgeNotFoundError` | 404: no such site, device or operation |
296
+ | `SolarEdgeRateLimitError` | 429: per-minute or monthly quota exceeded. Carries `retry_after` and `limits` (the `x-ratelimit-*` headers) |
297
+ | `SolarEdgeServerError` | 5xx: the API failed to handle a valid request |
298
+ | `SolarEdgeResponseError` | Success status, but the body could not be parsed |
299
+
300
+ `SolarEdgeAPIError` and its subclasses carry `status_code`, `response_body` and
301
+ `url`.
302
+
303
+ ## Rate Limits
304
+
305
+ Every call costs one credit. Plans set a monthly credit quota and a
306
+ calls-per-minute limit; the Free tier allows 2,000 credits a month and 10 calls
307
+ a minute.
308
+
309
+ This client caps itself at 3 concurrent requests but does not retry or throttle
310
+ for you. A client that polled several endpoints every 15 minutes under V1 will
311
+ run through the Free tier's monthly quota. To stay within it:
312
+
313
+ - Prefer the bulk telemetry methods over per-device ones.
314
+ - Use coarser resolutions where you can.
315
+ - Poll less often.
316
+
317
+ ## Migrating from 2.x
318
+
319
+ | 2.x method | 3.0 replacement |
320
+ | --- | --- |
321
+ | `get_site_list(...)` | `get_sites(page=..., sites_in_page=..., name=...)` |
322
+ | `get_site_details(site_id)` | `get_site_details(site_id)` |
323
+ | `get_overview(site_ids)` | `get_overview(site_id)`, one site per call |
324
+ | `get_energy(site_ids, start_date, end_date, time_unit)` | `get_energy(site_id, start, end, resolution, unit)` |
325
+ | `get_time_frame_energy(...)` | `get_energy(..., resolution="TOTAL")` |
326
+ | `get_power(site_id, start_time, end_time)` | `get_power(site_id, start, end, resolution, unit)` |
327
+ | `get_power_details(...)`, `get_energy_details(...)` | `get_meters_telemetry(...)`, or `get_power_per_destination` / `get_energy_per_destination` (Advanced) |
328
+ | `get_current_power_flow(site_id)` | `get_current_power_flow(site_id)` (Advanced) |
329
+ | `get_storage_data(...)` | `get_storage_telemetry(...)`, `get_storage_status(...)` |
330
+ | `get_environmental_benefits(site_id, system_units)` | `get_environmental_benefits(site_id, unit="METRIC" or "IMPERIAL")` |
331
+ | `get_inventory(site_id)`, `get_components_list(site_id)` | `get_devices(site_id, types=[...])` |
332
+ | `get_inverter_technical_data(site_id, serial, ...)` | `get_inverters_telemetry(...)` and `get_inverters_status(...)` with `serial_numbers=[serial]` |
333
+ | `get_meters(...)` | `get_meters_lifetime_energy(...)`, `get_meters_telemetry(...)` |
334
+ | `get_site_data(site_ids)` | No V2 equivalent |
335
+
336
+ Other changes:
337
+
338
+ - **Credentials:** V1 keys are rejected. Homeowners use Site Access (OAuth);
339
+ installers use a V2 Fleet Access API key. See [Credentials](#credentials).
340
+ - **One site per call:** V2 has no comma-separated bulk form for sites.
341
+ - **Datetimes must be timezone-aware**, and are sent as UTC `from`/`to`.
342
+ - **Parameters renamed:** `time_unit` is now `resolution`, and
343
+ `QUARTER_OF_AN_HOUR` is now `QUARTER_HOUR`. Enum values are upper case,
344
+ e.g. `METRIC`, `PRODUCTION`.
345
+ - **Response bodies differ**, so code that reads fields needs updating.
346
+ - **Data no longer available:** optimizer-level telemetry (optimizers appear
347
+ only in `get_devices`), and per-phase inverter values (V2 averages across
348
+ phases).
349
+
350
+ ## Development
351
+
352
+ ```bash
353
+ # Install development dependencies
354
+ uv sync
355
+ uv run prek install -t commit-msg
356
+ ```
357
+
358
+ ```bash
359
+ # Commiting changes
360
+ uv run cz c
361
+ ```
362
+
363
+ ## API Documentation
364
+
365
+ - [V2 API reference for this library](docs/SE_monitoring_API.md)
366
+ - [SolarEdge Developer Platform documentation](https://api-docs.solaredge.com/)
367
+ - [SolarEdge: Migrating from V1 to V2](https://api-docs.solaredge.com/docs/developer-platform/mxj41549n0nxk-migrating-from-v1-to-v2)