myelectricaldatapy 2.3.1__tar.gz → 2.4.1__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 (57) hide show
  1. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/.pre-commit-config.yaml +1 -0
  2. myelectricaldatapy-2.4.1/PKG-INFO +194 -0
  3. myelectricaldatapy-2.4.1/README.md +159 -0
  4. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/myelectricaldatapy/__init__.py +33 -4
  5. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/myelectricaldatapy/analytics.py +49 -3
  6. myelectricaldatapy-2.4.1/myelectricaldatapy/api.py +312 -0
  7. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/myelectricaldatapy/auth.py +19 -10
  8. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/myelectricaldatapy/const.py +1 -1
  9. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/myelectricaldatapy/exceptions.py +9 -1
  10. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/myelectricaldatapy/mypdl.py +111 -115
  11. myelectricaldatapy-2.4.1/myelectricaldatapy/types.py +282 -0
  12. myelectricaldatapy-2.4.1/myelectricaldatapy.egg-info/PKG-INFO +194 -0
  13. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/myelectricaldatapy.egg-info/SOURCES.txt +1 -0
  14. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/myelectricaldatapy.egg-info/requires.txt +1 -0
  15. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/myelectricaldatapy.egg-info/scm_file_list.json +1 -0
  16. myelectricaldatapy-2.4.1/myelectricaldatapy.egg-info/scm_version.json +8 -0
  17. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/pyproject.toml +1 -0
  18. myelectricaldatapy-2.4.1/tests/conftest.py +115 -0
  19. myelectricaldatapy-2.4.1/tests/fixtures/identity.json +10 -0
  20. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/tests/test_analytics.py +38 -56
  21. myelectricaldatapy-2.4.1/tests/test_load_data.py +239 -0
  22. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/uv.lock +113 -0
  23. myelectricaldatapy-2.3.1/PKG-INFO +0 -154
  24. myelectricaldatapy-2.3.1/README.md +0 -120
  25. myelectricaldatapy-2.3.1/myelectricaldatapy/api.py +0 -213
  26. myelectricaldatapy-2.3.1/myelectricaldatapy/types.py +0 -71
  27. myelectricaldatapy-2.3.1/myelectricaldatapy.egg-info/PKG-INFO +0 -154
  28. myelectricaldatapy-2.3.1/myelectricaldatapy.egg-info/scm_version.json +0 -8
  29. myelectricaldatapy-2.3.1/tests/conftest.py +0 -106
  30. myelectricaldatapy-2.3.1/tests/test_load_data.py +0 -229
  31. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/.devcontainer/devcontainer-lock.json +0 -0
  32. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/.devcontainer/devcontainer.json +0 -0
  33. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/.github/dependabot.yml +0 -0
  34. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/.github/workflows/auto-approve.yml +0 -0
  35. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/.github/workflows/python-lint.yml +0 -0
  36. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/.github/workflows/python-publish.yml +0 -0
  37. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/.github/workflows/release.yml +0 -0
  38. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/.gitignore +0 -0
  39. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/.vscode/settings.json +0 -0
  40. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/LICENSE +0 -0
  41. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/_secrets.yaml +0 -0
  42. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/example.py +0 -0
  43. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/myelectricaldatapy/py.typed +0 -0
  44. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/myelectricaldatapy/tz.py +0 -0
  45. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/myelectricaldatapy.egg-info/dependency_links.txt +0 -0
  46. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/myelectricaldatapy.egg-info/top_level.txt +0 -0
  47. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/setup.cfg +0 -0
  48. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/tests/__init__.py +0 -0
  49. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/tests/consts.py +0 -0
  50. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/tests/fixtures/access.json +0 -0
  51. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/tests/fixtures/address.json +0 -0
  52. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/tests/fixtures/contract.json +0 -0
  53. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/tests/fixtures/daily.json +0 -0
  54. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/tests/fixtures/detail.json +0 -0
  55. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/tests/fixtures/ecowatt.json +0 -0
  56. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/tests/fixtures/invalid_access.json +0 -0
  57. {myelectricaldatapy-2.3.1 → myelectricaldatapy-2.4.1}/tests/fixtures/tempo.json +0 -0
@@ -50,4 +50,5 @@ repos:
50
50
  hooks:
51
51
  - id: mypy
52
52
  args: [--strict, --ignore-missing-imports]
53
+ additional_dependencies: [pydantic>=2.5]
53
54
  exclude: (tests)
@@ -0,0 +1,194 @@
1
+ Metadata-Version: 2.4
2
+ Name: myelectricaldatapy
3
+ Version: 2.4.1
4
+ Summary: Fetch Linky data from myelectricaldata.fr
5
+ Author-email: Cyr-ius <cyr-ius@ipocus.net>
6
+ License-Expression: GPL-3.0-or-later
7
+ Project-URL: Homepage, https://github.com/cyr-ius/myelectricaldatapy
8
+ Keywords: enedis,linky,async,myelectricaldata
9
+ Platform: any
10
+ Classifier: Programming Language :: Python
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Classifier: Framework :: AsyncIO
17
+ Classifier: Intended Audience :: Developers
18
+ Classifier: Topic :: Home Automation
19
+ Requires-Python: >=3.12
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: aiohttp>=3.8.1
23
+ Requires-Dist: pandas>=2.0.2
24
+ Requires-Dist: pydantic>=2.5
25
+ Provides-Extra: dev
26
+ Requires-Dist: prek; extra == "dev"
27
+ Requires-Dist: ruff; extra == "dev"
28
+ Requires-Dist: yamllint; extra == "dev"
29
+ Requires-Dist: pytest; extra == "dev"
30
+ Requires-Dist: pytest-asyncio; extra == "dev"
31
+ Requires-Dist: python-typing-update; extra == "dev"
32
+ Requires-Dist: typing_extensions; extra == "dev"
33
+ Requires-Dist: freezegun; extra == "dev"
34
+ Dynamic: license-file
35
+
36
+ # myelectricaldatapy
37
+
38
+ Fetch Enedis Linky data from myelectricaldata.fr (enedisgateway.tech)
39
+
40
+ ## Install
41
+
42
+ Use the PIP package manager
43
+
44
+ ```bash
45
+ $ pip install myelectricaldatapy
46
+ ```
47
+
48
+ Or manually download and install the last version from github
49
+
50
+ ```bash
51
+ $ git clone https://github.com/cyr-ius/myelectricaldatapy.git
52
+ $ pip install .
53
+ ```
54
+
55
+ ## Public API
56
+
57
+ ```python
58
+ from myelectricaldatapy import Enedis, EnedisByPDL, EnedisException
59
+ ```
60
+
61
+ - `Enedis` : thin async client, one method per myelectricaldata.fr endpoint.
62
+ - `EnedisByPDL` : higher level helper that aggregates every piece of
63
+ information for a single delivery point (PDL) and computes analytics.
64
+ - `EnedisException` : base exception. Every library error
65
+ (`HttpRequestError`, `LimitReached`, `PayloadError`,
66
+ `TimeoutExceededError`, `AnalyticsError`) derives from it.
67
+
68
+ All payloads are validated and returned as [pydantic](https://docs.pydantic.dev)
69
+ models (`Contract`, `UsagePoint`, `DataCollect`, `TempoDays`, `Prices`, ...).
70
+
71
+ ## `Enedis`
72
+
73
+ ```python
74
+ Enedis(token: str, session: ClientSession | None = None, timeout: int = 30)
75
+ ```
76
+
77
+ | Method | Description |
78
+ | ------------------------------------------------------------ | ----------------------------------- |
79
+ | `async_fetch_datas(service, pdl, start=None, end=None)` | Raw call to any service |
80
+ | `async_valid_access(pdl)` / `async_has_access(pdl)` | Access / quota status |
81
+ | `async_get_contract(pdl)` / `async_get_contracts(pdl)` | Contract(s) |
82
+ | `async_get_address(pdl)` / `async_get_addresses(pdl)` | Address(es) |
83
+ | `async_get_identity(pdl)` | Identity |
84
+ | `async_get_daily_consumption(pdl, start, end)` | Daily consumption (max 1095 days) |
85
+ | `async_get_daily_production(pdl, start, end)` | Daily production (max 1095 days) |
86
+ | `async_get_details_consumption(pdl, start, end)` | Load curve consumption (max 7 days) |
87
+ | `async_get_details_production(pdl, start, end)` | Load curve production (max 7 days) |
88
+ | `async_get_max_power(pdl, start, end)` | Max power |
89
+ | `async_get_ecowatt(start=None, end=None)` | Ecowatt forecast |
90
+ | `async_get_tempo(start=None, end=None)` | Tempo day colors on a range |
91
+ | `async_get_tempo_days()` | Tempo calendar |
92
+ | `async_get_tempo_prices()` | Tempo prices |
93
+ | `async_has_offpeak(pdl)` / `async_check_offpeak(pdl, start)` | Off-peak helpers |
94
+ | `async_close()` | Close the aiohttp session |
95
+
96
+ ## `EnedisByPDL`
97
+
98
+ ```python
99
+ EnedisByPDL(
100
+ pdl: str,
101
+ token: str,
102
+ subscription: str = "standard", # "standard" | "hphc" | "tempo"
103
+ session: ClientSession | None = None,
104
+ timeout: int = 30,
105
+ timezone: tzinfo | None = None,
106
+ )
107
+ ```
108
+
109
+ Workflow:
110
+
111
+ 1. Optionally declare what you want to collect with
112
+ `set_data_fetch(...)`, `set_ecowatt_subscription(True)` and
113
+ `set_maxpower_subscription(True)`.
114
+ 2. Call `await async_update()` (or `async_update(force_refresh=True)`).
115
+ 3. Read the aggregated results.
116
+
117
+ | Member | Description |
118
+ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
119
+ | `set_data_fetch(service, start=None, end=None, intervals=None, prices=None, cum_value=None, cum_price=None)` | Register a collect (`service` is one of `DAILY_CONSUM`, `DAILY_PROD`, `DETAIL_CONSUM`, `DETAIL_PROD`) |
120
+ | `set_ecowatt_subscription(activate=False)` | Enable Ecowatt retrieval |
121
+ | `set_maxpower_subscription(activate=False)` | Enable max power retrieval |
122
+ | `async_update(force_refresh=False)` | Refresh access, contract, address, tempo, ecowatt, collects |
123
+ | `async_update_collects()` | Refresh only the registered collects |
124
+ | `stats` | `dict` keyed by mode (`"consumption"` / `"production"`) with the computed analytics |
125
+ | `access`, `contract`, `address`, `ecowatt`, `max_power`, `tempo`, `tempo_days`, `tempo_prices` | Last fetched models |
126
+ | `is_connected`, `has_intervals`, `has_tempo_subscription`, `has_offpeak_hours_subscription`, `has_standard_subscription`, `has_ecowatt_subscription`, `has_maxpower_subscription` | Boolean state |
127
+ | `ecowatt_day`, `tempo_day`, `consum_prices`, `prod_prices` | Convenience accessors for today |
128
+ | `async_close()` | Close the aiohttp session |
129
+
130
+ ## Timezone
131
+
132
+ Enedis/RTE APIs return naive timestamps that represent local wall-clock time. This
133
+ library needs to know which timezone that is in order to compute things like
134
+ off-peak hours or hourly statistics correctly.
135
+
136
+ By default it falls back to the timezone of the host machine running the code
137
+ (`/etc/localtime`), but that has no reason to match the timezone your
138
+ application is actually configured for (e.g. Home Assistant's
139
+ `hass.config.time_zone`). Pass it explicitly to avoid a fixed-offset shift in
140
+ the reported times:
141
+
142
+ ```python
143
+ api = EnedisByPDL(pdl=PDL, token=TOKEN, timezone=ZoneInfo("Europe/Paris"))
144
+ ```
145
+
146
+ Alternatively, set it once globally for every instance:
147
+
148
+ ```python
149
+ from myelectricaldatapy import set_local_timezone
150
+
151
+ set_local_timezone(ZoneInfo("Europe/Paris"))
152
+ ```
153
+
154
+ ## Get started
155
+
156
+ ```python
157
+ import asyncio
158
+ from datetime import timedelta
159
+
160
+ from myelectricaldatapy import DETAIL_CONSUM, EnedisByPDL, EnedisException
161
+ from myelectricaldatapy.tz import local_now
162
+
163
+ TOKEN = "012345"
164
+ PDL = "012345012345"
165
+
166
+
167
+ async def main() -> None:
168
+ api = EnedisByPDL(pdl=PDL, token=TOKEN)
169
+
170
+ try:
171
+ # Off-peak intervals and per-interval prices are optional.
172
+ api.set_data_fetch(
173
+ DETAIL_CONSUM,
174
+ start=local_now() - timedelta(days=7),
175
+ end=local_now(),
176
+ intervals=[("08:00", "12:00")],
177
+ prices={"standard": {"price": 0.18}, "offpeak": {"price": 0.1641}},
178
+ )
179
+ await api.async_update()
180
+
181
+ print(api.contract)
182
+ print(api.address)
183
+ print(api.stats["consumption"])
184
+ except EnedisException as error:
185
+ print(error)
186
+ finally:
187
+ await api.async_close()
188
+
189
+
190
+ asyncio.run(main())
191
+ ```
192
+
193
+ Have a look at [example.py](https://github.com/cyr-ius/myelectricaldatapy/blob/master/example.py)
194
+ for a more complete overview.
@@ -0,0 +1,159 @@
1
+ # myelectricaldatapy
2
+
3
+ Fetch Enedis Linky data from myelectricaldata.fr (enedisgateway.tech)
4
+
5
+ ## Install
6
+
7
+ Use the PIP package manager
8
+
9
+ ```bash
10
+ $ pip install myelectricaldatapy
11
+ ```
12
+
13
+ Or manually download and install the last version from github
14
+
15
+ ```bash
16
+ $ git clone https://github.com/cyr-ius/myelectricaldatapy.git
17
+ $ pip install .
18
+ ```
19
+
20
+ ## Public API
21
+
22
+ ```python
23
+ from myelectricaldatapy import Enedis, EnedisByPDL, EnedisException
24
+ ```
25
+
26
+ - `Enedis` : thin async client, one method per myelectricaldata.fr endpoint.
27
+ - `EnedisByPDL` : higher level helper that aggregates every piece of
28
+ information for a single delivery point (PDL) and computes analytics.
29
+ - `EnedisException` : base exception. Every library error
30
+ (`HttpRequestError`, `LimitReached`, `PayloadError`,
31
+ `TimeoutExceededError`, `AnalyticsError`) derives from it.
32
+
33
+ All payloads are validated and returned as [pydantic](https://docs.pydantic.dev)
34
+ models (`Contract`, `UsagePoint`, `DataCollect`, `TempoDays`, `Prices`, ...).
35
+
36
+ ## `Enedis`
37
+
38
+ ```python
39
+ Enedis(token: str, session: ClientSession | None = None, timeout: int = 30)
40
+ ```
41
+
42
+ | Method | Description |
43
+ | ------------------------------------------------------------ | ----------------------------------- |
44
+ | `async_fetch_datas(service, pdl, start=None, end=None)` | Raw call to any service |
45
+ | `async_valid_access(pdl)` / `async_has_access(pdl)` | Access / quota status |
46
+ | `async_get_contract(pdl)` / `async_get_contracts(pdl)` | Contract(s) |
47
+ | `async_get_address(pdl)` / `async_get_addresses(pdl)` | Address(es) |
48
+ | `async_get_identity(pdl)` | Identity |
49
+ | `async_get_daily_consumption(pdl, start, end)` | Daily consumption (max 1095 days) |
50
+ | `async_get_daily_production(pdl, start, end)` | Daily production (max 1095 days) |
51
+ | `async_get_details_consumption(pdl, start, end)` | Load curve consumption (max 7 days) |
52
+ | `async_get_details_production(pdl, start, end)` | Load curve production (max 7 days) |
53
+ | `async_get_max_power(pdl, start, end)` | Max power |
54
+ | `async_get_ecowatt(start=None, end=None)` | Ecowatt forecast |
55
+ | `async_get_tempo(start=None, end=None)` | Tempo day colors on a range |
56
+ | `async_get_tempo_days()` | Tempo calendar |
57
+ | `async_get_tempo_prices()` | Tempo prices |
58
+ | `async_has_offpeak(pdl)` / `async_check_offpeak(pdl, start)` | Off-peak helpers |
59
+ | `async_close()` | Close the aiohttp session |
60
+
61
+ ## `EnedisByPDL`
62
+
63
+ ```python
64
+ EnedisByPDL(
65
+ pdl: str,
66
+ token: str,
67
+ subscription: str = "standard", # "standard" | "hphc" | "tempo"
68
+ session: ClientSession | None = None,
69
+ timeout: int = 30,
70
+ timezone: tzinfo | None = None,
71
+ )
72
+ ```
73
+
74
+ Workflow:
75
+
76
+ 1. Optionally declare what you want to collect with
77
+ `set_data_fetch(...)`, `set_ecowatt_subscription(True)` and
78
+ `set_maxpower_subscription(True)`.
79
+ 2. Call `await async_update()` (or `async_update(force_refresh=True)`).
80
+ 3. Read the aggregated results.
81
+
82
+ | Member | Description |
83
+ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
84
+ | `set_data_fetch(service, start=None, end=None, intervals=None, prices=None, cum_value=None, cum_price=None)` | Register a collect (`service` is one of `DAILY_CONSUM`, `DAILY_PROD`, `DETAIL_CONSUM`, `DETAIL_PROD`) |
85
+ | `set_ecowatt_subscription(activate=False)` | Enable Ecowatt retrieval |
86
+ | `set_maxpower_subscription(activate=False)` | Enable max power retrieval |
87
+ | `async_update(force_refresh=False)` | Refresh access, contract, address, tempo, ecowatt, collects |
88
+ | `async_update_collects()` | Refresh only the registered collects |
89
+ | `stats` | `dict` keyed by mode (`"consumption"` / `"production"`) with the computed analytics |
90
+ | `access`, `contract`, `address`, `ecowatt`, `max_power`, `tempo`, `tempo_days`, `tempo_prices` | Last fetched models |
91
+ | `is_connected`, `has_intervals`, `has_tempo_subscription`, `has_offpeak_hours_subscription`, `has_standard_subscription`, `has_ecowatt_subscription`, `has_maxpower_subscription` | Boolean state |
92
+ | `ecowatt_day`, `tempo_day`, `consum_prices`, `prod_prices` | Convenience accessors for today |
93
+ | `async_close()` | Close the aiohttp session |
94
+
95
+ ## Timezone
96
+
97
+ Enedis/RTE APIs return naive timestamps that represent local wall-clock time. This
98
+ library needs to know which timezone that is in order to compute things like
99
+ off-peak hours or hourly statistics correctly.
100
+
101
+ By default it falls back to the timezone of the host machine running the code
102
+ (`/etc/localtime`), but that has no reason to match the timezone your
103
+ application is actually configured for (e.g. Home Assistant's
104
+ `hass.config.time_zone`). Pass it explicitly to avoid a fixed-offset shift in
105
+ the reported times:
106
+
107
+ ```python
108
+ api = EnedisByPDL(pdl=PDL, token=TOKEN, timezone=ZoneInfo("Europe/Paris"))
109
+ ```
110
+
111
+ Alternatively, set it once globally for every instance:
112
+
113
+ ```python
114
+ from myelectricaldatapy import set_local_timezone
115
+
116
+ set_local_timezone(ZoneInfo("Europe/Paris"))
117
+ ```
118
+
119
+ ## Get started
120
+
121
+ ```python
122
+ import asyncio
123
+ from datetime import timedelta
124
+
125
+ from myelectricaldatapy import DETAIL_CONSUM, EnedisByPDL, EnedisException
126
+ from myelectricaldatapy.tz import local_now
127
+
128
+ TOKEN = "012345"
129
+ PDL = "012345012345"
130
+
131
+
132
+ async def main() -> None:
133
+ api = EnedisByPDL(pdl=PDL, token=TOKEN)
134
+
135
+ try:
136
+ # Off-peak intervals and per-interval prices are optional.
137
+ api.set_data_fetch(
138
+ DETAIL_CONSUM,
139
+ start=local_now() - timedelta(days=7),
140
+ end=local_now(),
141
+ intervals=[("08:00", "12:00")],
142
+ prices={"standard": {"price": 0.18}, "offpeak": {"price": 0.1641}},
143
+ )
144
+ await api.async_update()
145
+
146
+ print(api.contract)
147
+ print(api.address)
148
+ print(api.stats["consumption"])
149
+ except EnedisException as error:
150
+ print(error)
151
+ finally:
152
+ await api.async_close()
153
+
154
+
155
+ asyncio.run(main())
156
+ ```
157
+
158
+ Have a look at [example.py](https://github.com/cyr-ius/myelectricaldatapy/blob/master/example.py)
159
+ for a more complete overview.
@@ -3,46 +3,75 @@
3
3
  from .api import Enedis
4
4
  from .const import *
5
5
  from .exceptions import (
6
+ AnalyticsError,
6
7
  EnedisException,
7
8
  HttpRequestError,
8
9
  LimitReached,
10
+ PayloadError,
9
11
  TimeoutExceededError,
10
12
  )
11
- from .mypdl import EnedisByPDL, FormatError, validate_cumsum, validate_prices
13
+ from .mypdl import EnedisByPDL
12
14
  from .types import (
15
+ AccessResponse,
13
16
  ConsumptionCollect,
17
+ Contract,
14
18
  Cum,
19
+ Customer,
20
+ CustomerResponse,
21
+ DataCollect,
22
+ EcowattDay,
15
23
  EnergyCollect,
24
+ IntervalReading,
25
+ MeterReading,
16
26
  Mode,
17
27
  Prices,
18
28
  ProductionCollect,
29
+ ReadingType,
19
30
  Service,
20
31
  StandardPrice,
21
32
  Subscription,
33
+ TempoDays,
34
+ TempoLabels,
22
35
  TempoPrice,
36
+ UsagePoint,
37
+ UsagePointAddress,
38
+ UsagePointWrapper,
23
39
  )
24
40
  from .tz import get_local_timezone, set_local_timezone
25
41
 
26
42
  __all__ = [
43
+ "AccessResponse",
44
+ "AnalyticsError",
27
45
  "ConsumptionCollect",
46
+ "Contract",
28
47
  "Cum",
48
+ "Customer",
49
+ "CustomerResponse",
50
+ "DataCollect",
51
+ "EcowattDay",
29
52
  "Enedis",
30
53
  "EnedisByPDL",
31
54
  "EnedisException",
32
55
  "EnergyCollect",
33
- "FormatError",
34
56
  "HttpRequestError",
57
+ "IntervalReading",
35
58
  "LimitReached",
59
+ "MeterReading",
36
60
  "Mode",
61
+ "PayloadError",
37
62
  "Prices",
38
63
  "ProductionCollect",
64
+ "ReadingType",
39
65
  "Service",
40
66
  "StandardPrice",
41
67
  "Subscription",
68
+ "TempoDays",
69
+ "TempoLabels",
42
70
  "TempoPrice",
43
71
  "TimeoutExceededError",
72
+ "UsagePoint",
73
+ "UsagePointAddress",
74
+ "UsagePointWrapper",
44
75
  "get_local_timezone",
45
76
  "set_local_timezone",
46
- "validate_cumsum",
47
- "validate_prices",
48
77
  ]
@@ -7,6 +7,8 @@ from typing import Any
7
7
  import pandas as pd
8
8
 
9
9
  from .const import ATTR_OFFPEAK, ATTR_STANDARD, TEMPO_DAYS
10
+ from .exceptions import AnalyticsError, EnedisException
11
+ from .types import TempoLabels
10
12
  from .tz import get_local_timezone
11
13
 
12
14
 
@@ -15,7 +17,12 @@ class EnedisAnalytics:
15
17
 
16
18
  def __init__(self, data: Any, timezone: _tzinfo | None = None) -> None:
17
19
  """Initialize Dataframe."""
18
- self.df = pd.DataFrame(data)
20
+ try:
21
+ self.df = pd.DataFrame(data)
22
+ except Exception as error: # normalized below
23
+ raise AnalyticsError(
24
+ f"Could not build a dataframe from the provided data: {error}"
25
+ ) from error
19
26
  # Resolved per instance (not at import/class-definition time) so a
20
27
  # timezone set later via tz.set_local_timezone(), or passed here
21
28
  # explicitly, is honored.
@@ -32,7 +39,46 @@ class EnedisAnalytics:
32
39
  cum_value: dict[str, Any] | None = None,
33
40
  cum_price: dict[str, Any] | None = None,
34
41
  prices: dict[str, Any] | None = None,
35
- tempo: dict[str, str] | None = None,
42
+ tempo: dict[str, TempoLabels] | None = None,
43
+ ) -> Any:
44
+ """Convert data to analyze.
45
+
46
+ Any failure while crunching the dataframe is re-raised as
47
+ :class:`AnalyticsError` so callers only have to catch
48
+ :class:`EnedisException`.
49
+ """
50
+ try:
51
+ return self._get_data_analytics(
52
+ convertKwh=convertKwh,
53
+ convertUTC=convertUTC,
54
+ start_date=start_date,
55
+ intervals=intervals,
56
+ groupby=groupby,
57
+ summary=summary,
58
+ cum_value=cum_value,
59
+ cum_price=cum_price,
60
+ prices=prices,
61
+ tempo=tempo,
62
+ )
63
+ except EnedisException:
64
+ raise
65
+ except Exception as error: # normalized below
66
+ raise AnalyticsError(
67
+ f"Failed to compute analytics from the provided dataset: {error}"
68
+ ) from error
69
+
70
+ def _get_data_analytics(
71
+ self,
72
+ convertKwh: bool = False,
73
+ convertUTC: bool = False,
74
+ start_date: dt | None = None,
75
+ intervals: list[tuple[str, str]] | None = None,
76
+ groupby: bool = False,
77
+ summary: bool = False,
78
+ cum_value: dict[str, Any] | None = None,
79
+ cum_price: dict[str, Any] | None = None,
80
+ prices: dict[str, Any] | None = None,
81
+ tempo: dict[str, TempoLabels] | None = None,
36
82
  ) -> Any:
37
83
  """Convert data to analyze."""
38
84
 
@@ -162,7 +208,7 @@ class EnedisAnalytics:
162
208
 
163
209
  return self.df
164
210
 
165
- def _set_tempo_days(self, tempo: dict[str, str]) -> None:
211
+ def _set_tempo_days(self, tempo: dict[str, TempoLabels]) -> None:
166
212
  """Add columns with tempo day."""
167
213
  for str_date, value in tempo.items():
168
214
  dt_date = pd.to_datetime(str_date, format="%Y-%m-%d")