myelectricaldatapy 2.2.0__tar.gz → 2.2.2__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.
- myelectricaldatapy-2.2.0/.github/workflows/dependbot-auto-approve.yml → myelectricaldatapy-2.2.2/.github/workflows/auto-approve.yml +4 -9
- {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/.gitignore +2 -2
- {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/.pre-commit-config.yaml +4 -6
- {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/PKG-INFO +1 -1
- {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/example.py +21 -10
- myelectricaldatapy-2.2.2/myelectricaldatapy/auth.py +68 -0
- {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/myelectricaldatapy/const.py +1 -0
- {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/myelectricaldatapy/myelectricaldata.py +47 -57
- {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/myelectricaldatapy/mypdl.py +21 -18
- {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/myelectricaldatapy.egg-info/PKG-INFO +1 -1
- {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/myelectricaldatapy.egg-info/SOURCES.txt +10 -2
- {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/pyproject.toml +6 -6
- myelectricaldatapy-2.2.2/tests/__init__.py +9 -0
- myelectricaldatapy-2.2.2/tests/conftest.py +125 -0
- myelectricaldatapy-2.2.2/tests/consts.py +4 -0
- myelectricaldatapy-2.2.2/tests/fixtures/access.json +10 -0
- myelectricaldatapy-2.2.2/tests/fixtures/address.json +23 -0
- myelectricaldatapy-2.2.2/tests/fixtures/contract.json +23 -0
- myelectricaldatapy-2.2.2/tests/fixtures/daily.json +381 -0
- myelectricaldatapy-2.2.2/tests/fixtures/detail.json +879 -0
- myelectricaldatapy-2.2.2/tests/fixtures/ecowatt.json +62 -0
- myelectricaldatapy-2.2.2/tests/fixtures/invalid_access.json +11 -0
- myelectricaldatapy-2.2.2/tests/fixtures/tempo.json +1 -0
- {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/tests/test_analytics.py +41 -5
- {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/tests/test_load_data.py +22 -10
- myelectricaldatapy-2.2.0/myelectricaldatapy/auth.py +0 -84
- myelectricaldatapy-2.2.0/tests/__init__.py +0 -0
- myelectricaldatapy-2.2.0/tests/conftest.py +0 -46
- myelectricaldatapy-2.2.0/tests/consts.py +0 -1425
- {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/.flake8 +0 -0
- {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/.github/dependabot.yml +0 -0
- {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/.github/workflows/lint.yml +0 -0
- {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/.github/workflows/pythonpublish.yml +0 -0
- {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/.github/workflows/release.yml +0 -0
- {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/.vscode/settings.json +0 -0
- {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/LICENSE +0 -0
- {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/MANIFEST.in +0 -0
- {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/README.md +0 -0
- {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/myelectricaldatapy/__init__.py +0 -0
- {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/myelectricaldatapy/analytics.py +0 -0
- {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/myelectricaldatapy/exceptions.py +0 -0
- {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/myelectricaldatapy/py.typed +0 -0
- {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/myelectricaldatapy.egg-info/dependency_links.txt +0 -0
- {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/myelectricaldatapy.egg-info/requires.txt +0 -0
- {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/myelectricaldatapy.egg-info/top_level.txt +0 -0
- {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/requirements-dev.txt +0 -0
- {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/requirements.txt +0 -0
- {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/setup.cfg +0 -0
|
@@ -1,26 +1,21 @@
|
|
|
1
|
-
name:
|
|
1
|
+
name: Dependency auto-merge
|
|
2
2
|
on: pull_request
|
|
3
3
|
|
|
4
4
|
permissions:
|
|
5
5
|
pull-requests: write
|
|
6
|
+
contents: write
|
|
6
7
|
|
|
7
8
|
jobs:
|
|
8
|
-
|
|
9
|
+
dependency:
|
|
9
10
|
runs-on: ubuntu-latest
|
|
10
|
-
if: github.actor == 'dependabot[bot]'
|
|
11
|
+
if: ${{ github.actor == 'dependabot[bot]' || github.actor == 'pre-commit-ci[bot]' }}
|
|
11
12
|
steps:
|
|
12
|
-
- name: Dependabot metadata
|
|
13
|
-
id: metadata
|
|
14
|
-
uses: dependabot/fetch-metadata@v2
|
|
15
|
-
with:
|
|
16
|
-
github-token: "${{ secrets.GITHUB_TOKEN }}"
|
|
17
13
|
- name: Approve a PR
|
|
18
14
|
run: gh pr review --approve "$PR_URL"
|
|
19
15
|
env:
|
|
20
16
|
PR_URL: ${{github.event.pull_request.html_url}}
|
|
21
17
|
GH_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
|
22
18
|
- name: Enable auto-merge for Dependabot PRs
|
|
23
|
-
if: contains(steps.metadata.outputs.dependency-names, 'my-dependency') && steps.metadata.outputs.update-type == 'version-update:semver-patch'
|
|
24
19
|
run: gh pr merge --auto --merge "$PR_URL"
|
|
25
20
|
env:
|
|
26
21
|
PR_URL: ${{github.event.pull_request.html_url}}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
repos:
|
|
3
3
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
4
|
-
rev: v0.4.
|
|
4
|
+
rev: v0.4.5
|
|
5
5
|
hooks:
|
|
6
6
|
- id: ruff
|
|
7
7
|
args:
|
|
8
8
|
- --fix
|
|
9
9
|
- id: ruff-format
|
|
10
10
|
- repo: https://github.com/codespell-project/codespell
|
|
11
|
-
rev: v2.
|
|
11
|
+
rev: v2.3.0
|
|
12
12
|
hooks:
|
|
13
13
|
- id: codespell
|
|
14
14
|
args:
|
|
@@ -45,11 +45,9 @@ repos:
|
|
|
45
45
|
- --py311-plus
|
|
46
46
|
- --force
|
|
47
47
|
- --keep-updates
|
|
48
|
-
files: ^(/.+)?[^/]+\.py$
|
|
49
48
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
|
50
|
-
rev: v1.
|
|
49
|
+
rev: v1.10.0
|
|
51
50
|
hooks:
|
|
52
51
|
- id: mypy
|
|
53
52
|
args: [--strict, --ignore-missing-imports]
|
|
54
|
-
|
|
55
|
-
exclude: tests/
|
|
53
|
+
exclude: (tests)
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
2
|
"""Example code."""
|
|
4
3
|
|
|
5
4
|
import asyncio
|
|
6
|
-
import logging
|
|
7
5
|
from datetime import datetime, timedelta
|
|
6
|
+
import logging
|
|
7
|
+
|
|
8
|
+
import yaml
|
|
8
9
|
|
|
9
10
|
from myelectricaldatapy import Enedis, EnedisByPDL, EnedisException
|
|
10
11
|
|
|
@@ -16,12 +17,16 @@ formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(messag
|
|
|
16
17
|
ch.setFormatter(formatter)
|
|
17
18
|
logger.addHandler(ch)
|
|
18
19
|
|
|
19
|
-
#
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
# Fill out the secrets in secrets.yaml, you can find an example
|
|
21
|
+
# _secrets.yaml file, which has to be renamed after filling out the secrets.
|
|
22
|
+
with open("./secrets.yaml", encoding="UTF-8") as file:
|
|
23
|
+
secrets = yaml.safe_load(file)
|
|
24
|
+
|
|
25
|
+
TOKEN = secrets["TOKEN"]
|
|
26
|
+
PDL = secrets["PDL"]
|
|
22
27
|
|
|
23
28
|
|
|
24
|
-
async def
|
|
29
|
+
async def async_main() -> None:
|
|
25
30
|
"""Main function."""
|
|
26
31
|
|
|
27
32
|
api = Enedis(token=TOKEN)
|
|
@@ -38,10 +43,16 @@ async def main() -> None:
|
|
|
38
43
|
except EnedisException as error:
|
|
39
44
|
logger.error(error)
|
|
40
45
|
|
|
46
|
+
await api.async_close()
|
|
47
|
+
|
|
41
48
|
myPdl = EnedisByPDL(PDL, TOKEN)
|
|
42
|
-
|
|
43
|
-
|
|
49
|
+
try:
|
|
50
|
+
await myPdl.async_update()
|
|
51
|
+
logger.info(myPdl.stats)
|
|
52
|
+
except EnedisException as error:
|
|
53
|
+
logger.error(error)
|
|
44
54
|
|
|
45
55
|
|
|
46
|
-
|
|
47
|
-
loop.
|
|
56
|
+
if __name__ == "__main__":
|
|
57
|
+
loop = asyncio.get_event_loop()
|
|
58
|
+
loop.run_until_complete(async_main())
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Class for Enedis Authentication (http://www.myelectricaldata.fr)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import json
|
|
7
|
+
import logging
|
|
8
|
+
import socket
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from aiohttp import ClientError, ClientResponseError, ClientSession
|
|
12
|
+
|
|
13
|
+
from .const import TIMEOUT, URL
|
|
14
|
+
from .exceptions import (
|
|
15
|
+
EnedisException,
|
|
16
|
+
HttpRequestError,
|
|
17
|
+
LimitReached,
|
|
18
|
+
TimeoutExceededError,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
_LOGGER = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class EnedisAuth:
|
|
25
|
+
"""Class for Enedis Auth API."""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self, session: ClientSession, token: str, timeout: int = TIMEOUT
|
|
29
|
+
) -> None:
|
|
30
|
+
"""Init."""
|
|
31
|
+
self.token = token
|
|
32
|
+
self.timeout = timeout
|
|
33
|
+
self.session = session
|
|
34
|
+
|
|
35
|
+
async def async_request(self, path: str, method: str = "get", **kwargs: Any) -> Any:
|
|
36
|
+
"""Request session."""
|
|
37
|
+
kwargs.setdefault("headers", {})
|
|
38
|
+
kwargs["headers"].update(
|
|
39
|
+
{"Content-Type": "application/json", "Authorization": self.token}
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
async with asyncio.timeout(self.timeout):
|
|
44
|
+
_LOGGER.debug("Request: %s (%s) - %s", path, method, kwargs.get("json"))
|
|
45
|
+
response = await self.session.request(method, f"{URL}/{path}", **kwargs)
|
|
46
|
+
contents = await response.read()
|
|
47
|
+
response.raise_for_status()
|
|
48
|
+
except (asyncio.CancelledError, asyncio.TimeoutError) as error:
|
|
49
|
+
raise TimeoutExceededError(
|
|
50
|
+
"Timeout occurred while connecting to MyElectricalData."
|
|
51
|
+
) from error
|
|
52
|
+
except ClientResponseError:
|
|
53
|
+
message = contents.decode("utf8")
|
|
54
|
+
if "application/json" in response.headers.get("Content-Type", ""):
|
|
55
|
+
if response.status == 409:
|
|
56
|
+
raise LimitReached(json.loads(message))
|
|
57
|
+
raise EnedisException(json.loads(message))
|
|
58
|
+
raise EnedisException({"message": message})
|
|
59
|
+
except (ClientError, socket.gaierror) as error:
|
|
60
|
+
raise HttpRequestError(
|
|
61
|
+
"Error occurred while communicating with MyElectricalData."
|
|
62
|
+
) from error
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
await response.json()
|
|
66
|
+
if "application/json" in response.headers.get("Content-Type", "")
|
|
67
|
+
else await response.text()
|
|
68
|
+
)
|
{myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/myelectricaldatapy/myelectricaldata.py
RENAMED
|
@@ -5,11 +5,13 @@ from __future__ import annotations
|
|
|
5
5
|
from datetime import date, datetime as dt, timedelta
|
|
6
6
|
import logging
|
|
7
7
|
import re
|
|
8
|
-
from types import TracebackType
|
|
9
8
|
from typing import Any, Generator, cast
|
|
10
9
|
|
|
10
|
+
from aiohttp import ClientSession
|
|
11
|
+
|
|
11
12
|
from .auth import EnedisAuth
|
|
12
13
|
from .const import DAILY_CONSUM, DAILY_PROD, DETAIL_CONSUM, DETAIL_PROD, TIMEOUT
|
|
14
|
+
from .exceptions import EnedisException
|
|
13
15
|
|
|
14
16
|
_LOGGER = logging.getLogger(__name__)
|
|
15
17
|
|
|
@@ -18,10 +20,14 @@ class Enedis:
|
|
|
18
20
|
"""Get data of pdl."""
|
|
19
21
|
|
|
20
22
|
def __init__(
|
|
21
|
-
self,
|
|
23
|
+
self,
|
|
24
|
+
token: str,
|
|
25
|
+
session: ClientSession = ClientSession(),
|
|
26
|
+
timeout: int = TIMEOUT,
|
|
22
27
|
) -> None:
|
|
23
28
|
"""Initialize."""
|
|
24
|
-
self.auth = EnedisAuth(
|
|
29
|
+
self.auth = EnedisAuth(session, token, timeout)
|
|
30
|
+
self.async_request = self.auth.async_request
|
|
25
31
|
self.offpeaks: list[str] = []
|
|
26
32
|
self.dt_offpeak: list[dt] = []
|
|
27
33
|
self.last_access: date | None = None
|
|
@@ -43,7 +49,7 @@ class Enedis:
|
|
|
43
49
|
end_date = end.strftime("%Y-%m-%d")
|
|
44
50
|
path_range = f"/start/{start_date}/end/{end_date}"
|
|
45
51
|
path = f"{service}/{pdl}{path_range}"
|
|
46
|
-
return await self.
|
|
52
|
+
return await self.async_request(path=path)
|
|
47
53
|
|
|
48
54
|
async def async_valid_access(self, pdl: str) -> Any:
|
|
49
55
|
"""Return valid access."""
|
|
@@ -103,7 +109,7 @@ class Enedis:
|
|
|
103
109
|
if end
|
|
104
110
|
else (dt.now() + timedelta(days=1)).strftime("%Y-%m-%d")
|
|
105
111
|
)
|
|
106
|
-
return await self.auth.
|
|
112
|
+
return await self.auth.async_request(path=f"rte/tempo/{str_start}/{str_end}")
|
|
107
113
|
|
|
108
114
|
async def async_get_ecowatt(
|
|
109
115
|
self, start: dt | None = None, end: dt | None = None
|
|
@@ -117,7 +123,7 @@ class Enedis:
|
|
|
117
123
|
if end
|
|
118
124
|
else (dt.now() + timedelta(days=1)).strftime("%Y-%m-%d")
|
|
119
125
|
)
|
|
120
|
-
return await self.
|
|
126
|
+
return await self.async_request(path=f"rte/ecowatt/{str_start}/{str_end}")
|
|
121
127
|
|
|
122
128
|
async def async_has_offpeak(self, pdl: str) -> bool:
|
|
123
129
|
"""Has offpeak hours."""
|
|
@@ -150,71 +156,42 @@ class Enedis:
|
|
|
150
156
|
|
|
151
157
|
async def async_get_details_consumption(self, pdl: str, start: dt, end: dt) -> Any:
|
|
152
158
|
"""Get consumption details. (max: 7 days)."""
|
|
153
|
-
|
|
154
|
-
for interval in list(self.date_range(start, end, 7)):
|
|
155
|
-
start, end = interval
|
|
156
|
-
try:
|
|
157
|
-
rsp = await self.async_fetch_datas(DETAIL_CONSUM, pdl, start, end)
|
|
158
|
-
finally:
|
|
159
|
-
if (
|
|
160
|
-
rsp is None
|
|
161
|
-
or rsp.get("meter_reading", {}).get("interval_reading") is None
|
|
162
|
-
):
|
|
163
|
-
continue
|
|
164
|
-
elif data is None:
|
|
165
|
-
data = cast(dict[str, Any], rsp)
|
|
166
|
-
else:
|
|
167
|
-
data["meter_reading"]["interval_reading"].extend(
|
|
168
|
-
rsp.get("meter_reading", {}).get("interval_reading")
|
|
169
|
-
)
|
|
170
|
-
|
|
171
|
-
return data
|
|
159
|
+
return await self._async_get_details(DETAIL_CONSUM, pdl, start, end)
|
|
172
160
|
|
|
173
161
|
async def async_get_details_production(self, pdl: str, start: dt, end: dt) -> Any:
|
|
162
|
+
"""Get production details. (max: 7 days)."""
|
|
163
|
+
return await self._async_get_details(DETAIL_PROD, pdl, start, end)
|
|
164
|
+
|
|
165
|
+
async def async_get_max_power(self, pdl: str, start: dt, end: dt) -> Any:
|
|
166
|
+
"""Get consumption max power."""
|
|
167
|
+
return await self.async_fetch_datas(
|
|
168
|
+
"daily_consumption_max_power", pdl, start, end
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
async def _async_get_details(self, mode: str, pdl: str, start: dt, end: dt) -> Any:
|
|
174
172
|
"""Get production details. (max: 7 days)."""
|
|
175
173
|
data = None
|
|
174
|
+
response = {}
|
|
175
|
+
raise_error = False
|
|
176
176
|
for interval in list(self.date_range(start, end, 7)):
|
|
177
177
|
start, end = interval
|
|
178
178
|
try:
|
|
179
|
-
|
|
179
|
+
if raise_error is False:
|
|
180
|
+
response = await self.async_fetch_datas(mode, pdl, start, end)
|
|
181
|
+
except EnedisException as error:
|
|
182
|
+
raise_error = True
|
|
183
|
+
_LOGGER.error(error)
|
|
180
184
|
finally:
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
or rsp.get("meter_reading", {}).get("interval_reading") is None
|
|
184
|
-
):
|
|
185
|
+
new_data = response.get("meter_reading", {}).get("interval_reading")
|
|
186
|
+
if response is None or new_data is None:
|
|
185
187
|
continue
|
|
186
188
|
elif data is None:
|
|
187
|
-
data = cast(dict[str, Any],
|
|
189
|
+
data = cast(dict[str, Any], response)
|
|
188
190
|
else:
|
|
189
|
-
data["meter_reading"]["interval_reading"].extend(
|
|
190
|
-
rsp.get("meter_reading", {}).get("interval_reading")
|
|
191
|
-
)
|
|
191
|
+
data["meter_reading"]["interval_reading"].extend(new_data)
|
|
192
192
|
|
|
193
193
|
return data
|
|
194
194
|
|
|
195
|
-
async def async_get_max_power(self, pdl: str, start: dt, end: dt) -> Any:
|
|
196
|
-
"""Get consumption max power."""
|
|
197
|
-
return await self.async_fetch_datas(
|
|
198
|
-
"daily_consumption_max_power", pdl, start, end
|
|
199
|
-
)
|
|
200
|
-
|
|
201
|
-
async def __aenter__(self) -> Enedis:
|
|
202
|
-
"""Asynchronous enter."""
|
|
203
|
-
return self
|
|
204
|
-
|
|
205
|
-
async def __aexit__(
|
|
206
|
-
self,
|
|
207
|
-
exc_type: type[BaseException] | None,
|
|
208
|
-
exc_value: BaseException | None,
|
|
209
|
-
traceback: TracebackType | None,
|
|
210
|
-
) -> None:
|
|
211
|
-
"""Asynchronous exit."""
|
|
212
|
-
await self.close()
|
|
213
|
-
|
|
214
|
-
async def close(self) -> None:
|
|
215
|
-
"""Close the session."""
|
|
216
|
-
await self.auth.async_close()
|
|
217
|
-
|
|
218
195
|
@staticmethod
|
|
219
196
|
def date_range(start: dt, end: dt, intv: int) -> Generator[tuple[dt, dt], dt, None]:
|
|
220
197
|
"""Return range by interval date."""
|
|
@@ -228,3 +205,16 @@ class Enedis:
|
|
|
228
205
|
yield (start, s_end)
|
|
229
206
|
start = s_end
|
|
230
207
|
yield (start, end)
|
|
208
|
+
|
|
209
|
+
async def __aenter__(self) -> Enedis:
|
|
210
|
+
"""Asynchronous enter."""
|
|
211
|
+
return self
|
|
212
|
+
|
|
213
|
+
async def __aexit__(self, *_exc_info: object) -> None:
|
|
214
|
+
"""Async exit."""
|
|
215
|
+
await self.async_close()
|
|
216
|
+
|
|
217
|
+
async def async_close(self) -> None:
|
|
218
|
+
"""Close the session."""
|
|
219
|
+
if self.auth.session:
|
|
220
|
+
await self.auth.session.close()
|
|
@@ -6,6 +6,7 @@ from datetime import date, datetime as dt, timedelta
|
|
|
6
6
|
import logging
|
|
7
7
|
from typing import Any, Callable, Tuple
|
|
8
8
|
|
|
9
|
+
from aiohttp import ClientSession
|
|
9
10
|
import voluptuous as vol
|
|
10
11
|
|
|
11
12
|
from myelectricaldatapy import Enedis, EnedisException, LimitReached
|
|
@@ -15,6 +16,7 @@ from .const import (
|
|
|
15
16
|
ATTR_CUM_PRICE,
|
|
16
17
|
ATTR_CUM_VALUE,
|
|
17
18
|
ATTR_END,
|
|
19
|
+
ATTR_FN,
|
|
18
20
|
ATTR_INTERVALS,
|
|
19
21
|
ATTR_OFFPEAK,
|
|
20
22
|
ATTR_PRICES,
|
|
@@ -88,7 +90,7 @@ class EnedisByPDL:
|
|
|
88
90
|
self,
|
|
89
91
|
pdl: str,
|
|
90
92
|
token: str,
|
|
91
|
-
session:
|
|
93
|
+
session: ClientSession = ClientSession(),
|
|
92
94
|
timeout: int = TIMEOUT,
|
|
93
95
|
) -> None:
|
|
94
96
|
"""Initialize."""
|
|
@@ -300,7 +302,7 @@ class EnedisByPDL:
|
|
|
300
302
|
func = funcs[service]
|
|
301
303
|
dt_start = start if start else dt.now() - timedelta(days=days)
|
|
302
304
|
dt_end = end if end else dt.now() + timedelta(days=1)
|
|
303
|
-
self._params[mode] = {
|
|
305
|
+
self._params[mode] = {ATTR_FN: func, ATTR_START: dt_start, ATTR_END: dt_end}
|
|
304
306
|
if intervals:
|
|
305
307
|
self._set_intervals(mode, intervals)
|
|
306
308
|
if prices:
|
|
@@ -315,23 +317,24 @@ class EnedisByPDL:
|
|
|
315
317
|
"""Update data to collect."""
|
|
316
318
|
checked = True
|
|
317
319
|
self.has_collected = False
|
|
318
|
-
for mode,
|
|
320
|
+
for mode, attr in self._params.items():
|
|
319
321
|
dataset = {}
|
|
320
|
-
start =
|
|
321
|
-
end =
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
_LOGGER.error(error.args[1]["detail"])
|
|
322
|
+
start = attr[ATTR_START]
|
|
323
|
+
end = attr[ATTR_END]
|
|
324
|
+
fn = attr[ATTR_FN]
|
|
325
|
+
try:
|
|
326
|
+
dataset = await fn(self.pdl, start, end)
|
|
327
|
+
except EnedisException as error:
|
|
328
|
+
checked = False
|
|
329
|
+
_LOGGER.error(error)
|
|
330
|
+
else:
|
|
330
331
|
data = dataset.get("meter_reading", {}).get("interval_reading", [])
|
|
331
|
-
if len(data)
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
332
|
+
if len(data) == 0:
|
|
333
|
+
raise EnedisException("Data collection is empty")
|
|
334
|
+
checked = checked and len(data) > 0
|
|
335
|
+
self._params[mode].update({"data": data})
|
|
336
|
+
|
|
337
|
+
if mode == CONSUMPTION and self._tempo_subs:
|
|
338
|
+
self.tempo = await self._api.async_get_tempo(start, end)
|
|
336
339
|
|
|
337
340
|
self.has_collected = checked
|
{myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/myelectricaldatapy.egg-info/SOURCES.txt
RENAMED
|
@@ -9,7 +9,7 @@ pyproject.toml
|
|
|
9
9
|
requirements-dev.txt
|
|
10
10
|
requirements.txt
|
|
11
11
|
.github/dependabot.yml
|
|
12
|
-
.github/workflows/
|
|
12
|
+
.github/workflows/auto-approve.yml
|
|
13
13
|
.github/workflows/lint.yml
|
|
14
14
|
.github/workflows/pythonpublish.yml
|
|
15
15
|
.github/workflows/release.yml
|
|
@@ -31,4 +31,12 @@ tests/__init__.py
|
|
|
31
31
|
tests/conftest.py
|
|
32
32
|
tests/consts.py
|
|
33
33
|
tests/test_analytics.py
|
|
34
|
-
tests/test_load_data.py
|
|
34
|
+
tests/test_load_data.py
|
|
35
|
+
tests/fixtures/access.json
|
|
36
|
+
tests/fixtures/address.json
|
|
37
|
+
tests/fixtures/contract.json
|
|
38
|
+
tests/fixtures/daily.json
|
|
39
|
+
tests/fixtures/detail.json
|
|
40
|
+
tests/fixtures/ecowatt.json
|
|
41
|
+
tests/fixtures/invalid_access.json
|
|
42
|
+
tests/fixtures/tempo.json
|
|
@@ -33,14 +33,14 @@ dependencies = [
|
|
|
33
33
|
|
|
34
34
|
[tool.setuptools_scm]
|
|
35
35
|
|
|
36
|
-
[tool.ruff.flake8-pytest-style]
|
|
36
|
+
[tool.ruff.lint.flake8-pytest-style]
|
|
37
37
|
fixture-parentheses = false
|
|
38
38
|
|
|
39
|
-
[tool.ruff.
|
|
40
|
-
"async_timeout".msg = "use asyncio.timeout instead"
|
|
41
|
-
"pytz".msg = "use zoneinfo instead"
|
|
42
|
-
|
|
43
|
-
[tool.ruff.isort]
|
|
39
|
+
[tool.ruff.lint.isort]
|
|
44
40
|
force-sort-within-sections = true
|
|
45
41
|
combine-as-imports = true
|
|
46
42
|
split-on-trailing-comma = false
|
|
43
|
+
|
|
44
|
+
[[tool.mypy.overrides]]
|
|
45
|
+
module = "yaml"
|
|
46
|
+
ignore_missing_imports = true
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""Test helpers for MyElectricalData."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Generator
|
|
4
|
+
import json
|
|
5
|
+
from typing import Any
|
|
6
|
+
from unittest.mock import AsyncMock, patch
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
import myelectricaldatapy
|
|
11
|
+
|
|
12
|
+
from . import load_fixture
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.fixture(name="mock_detail")
|
|
16
|
+
def mock_detail() -> dict[str, Any]:
|
|
17
|
+
return json.loads(load_fixture("detail.json"))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@pytest.fixture(name="mock_daily")
|
|
21
|
+
def mock_daily() -> dict[str, Any]:
|
|
22
|
+
return json.loads(load_fixture("daily.json"))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.fixture(name="mock_contract")
|
|
26
|
+
def mock_contract() -> dict[str, Any]:
|
|
27
|
+
return json.loads(load_fixture("contract.json"))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@pytest.fixture(name="mock_access")
|
|
31
|
+
def mock_access(request) -> dict[str, Any]:
|
|
32
|
+
if hasattr(request, "param") and request.param:
|
|
33
|
+
return json.loads(load_fixture("invalid_access.json"))
|
|
34
|
+
return json.loads(load_fixture("access.json"))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@pytest.fixture(name="mock_address")
|
|
38
|
+
def mock_address() -> dict[str, Any]:
|
|
39
|
+
return json.loads(load_fixture("address.json"))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@pytest.fixture(name="mock_tempo")
|
|
43
|
+
def mock_tempo(return_invalid: bool = False) -> dict[str, Any]:
|
|
44
|
+
return json.loads(load_fixture("tempo.json"))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@pytest.fixture(name="mock_ecowatt")
|
|
48
|
+
def mock_ecowatt(request) -> dict[str, Any]:
|
|
49
|
+
if hasattr(request, "param") and request.param:
|
|
50
|
+
return {
|
|
51
|
+
"detail": "Pas de données disponible entre la date du 2023-01-22 00:00:00 et 2023-01-22 00:00:00" # noqa
|
|
52
|
+
}
|
|
53
|
+
return json.loads(load_fixture("ecowatt.json"))
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@pytest.fixture(name="mock_enedis")
|
|
57
|
+
def mock_enedis(
|
|
58
|
+
mock_detail,
|
|
59
|
+
mock_daily,
|
|
60
|
+
mock_contract,
|
|
61
|
+
mock_access,
|
|
62
|
+
mock_address,
|
|
63
|
+
mock_tempo,
|
|
64
|
+
mock_ecowatt,
|
|
65
|
+
) -> Generator[AsyncMock, None, None]:
|
|
66
|
+
"""Mock a successful connection."""
|
|
67
|
+
|
|
68
|
+
with (
|
|
69
|
+
patch.object(
|
|
70
|
+
myelectricaldatapy.Enedis,
|
|
71
|
+
"async_get_daily_consumption",
|
|
72
|
+
return_value=mock_daily,
|
|
73
|
+
),
|
|
74
|
+
patch.object(
|
|
75
|
+
myelectricaldatapy.Enedis,
|
|
76
|
+
"async_get_daily_production",
|
|
77
|
+
return_value=mock_daily,
|
|
78
|
+
),
|
|
79
|
+
patch.object(
|
|
80
|
+
myelectricaldatapy.Enedis,
|
|
81
|
+
"async_get_details_consumption",
|
|
82
|
+
return_value=mock_detail,
|
|
83
|
+
),
|
|
84
|
+
patch.object(
|
|
85
|
+
myelectricaldatapy.Enedis, "async_valid_access", return_value=mock_access
|
|
86
|
+
),
|
|
87
|
+
patch.object(
|
|
88
|
+
myelectricaldatapy.Enedis, "async_get_contract", return_value=mock_contract
|
|
89
|
+
),
|
|
90
|
+
patch.object(
|
|
91
|
+
myelectricaldatapy.Enedis, "async_get_address", return_value=mock_address
|
|
92
|
+
),
|
|
93
|
+
patch.object(
|
|
94
|
+
myelectricaldatapy.Enedis, "async_get_tempo", return_value=mock_tempo
|
|
95
|
+
),
|
|
96
|
+
patch.object(
|
|
97
|
+
myelectricaldatapy.Enedis, "async_get_ecowatt", return_value=mock_ecowatt
|
|
98
|
+
),
|
|
99
|
+
):
|
|
100
|
+
yield AsyncMock()
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@pytest.fixture(name="mock_base")
|
|
104
|
+
def mock_base(
|
|
105
|
+
mock_access, mock_contract, mock_address, mock_tempo, mock_ecowatt
|
|
106
|
+
) -> Generator[AsyncMock, None, None]:
|
|
107
|
+
"""Mock a successful connection."""
|
|
108
|
+
with (
|
|
109
|
+
patch.object(
|
|
110
|
+
myelectricaldatapy.Enedis, "async_valid_access", return_value=mock_access
|
|
111
|
+
),
|
|
112
|
+
patch.object(
|
|
113
|
+
myelectricaldatapy.Enedis, "async_get_contract", return_value=mock_contract
|
|
114
|
+
),
|
|
115
|
+
patch.object(
|
|
116
|
+
myelectricaldatapy.Enedis, "async_get_address", return_value=mock_address
|
|
117
|
+
),
|
|
118
|
+
patch.object(
|
|
119
|
+
myelectricaldatapy.Enedis, "async_get_tempo", return_value=mock_tempo
|
|
120
|
+
),
|
|
121
|
+
patch.object(
|
|
122
|
+
myelectricaldatapy.Enedis, "async_get_ecowatt", return_value=mock_ecowatt
|
|
123
|
+
),
|
|
124
|
+
):
|
|
125
|
+
yield AsyncMock()
|