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.
Files changed (48) hide show
  1. myelectricaldatapy-2.2.0/.github/workflows/dependbot-auto-approve.yml → myelectricaldatapy-2.2.2/.github/workflows/auto-approve.yml +4 -9
  2. {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/.gitignore +2 -2
  3. {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/.pre-commit-config.yaml +4 -6
  4. {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/PKG-INFO +1 -1
  5. {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/example.py +21 -10
  6. myelectricaldatapy-2.2.2/myelectricaldatapy/auth.py +68 -0
  7. {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/myelectricaldatapy/const.py +1 -0
  8. {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/myelectricaldatapy/myelectricaldata.py +47 -57
  9. {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/myelectricaldatapy/mypdl.py +21 -18
  10. {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/myelectricaldatapy.egg-info/PKG-INFO +1 -1
  11. {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/myelectricaldatapy.egg-info/SOURCES.txt +10 -2
  12. {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/pyproject.toml +6 -6
  13. myelectricaldatapy-2.2.2/tests/__init__.py +9 -0
  14. myelectricaldatapy-2.2.2/tests/conftest.py +125 -0
  15. myelectricaldatapy-2.2.2/tests/consts.py +4 -0
  16. myelectricaldatapy-2.2.2/tests/fixtures/access.json +10 -0
  17. myelectricaldatapy-2.2.2/tests/fixtures/address.json +23 -0
  18. myelectricaldatapy-2.2.2/tests/fixtures/contract.json +23 -0
  19. myelectricaldatapy-2.2.2/tests/fixtures/daily.json +381 -0
  20. myelectricaldatapy-2.2.2/tests/fixtures/detail.json +879 -0
  21. myelectricaldatapy-2.2.2/tests/fixtures/ecowatt.json +62 -0
  22. myelectricaldatapy-2.2.2/tests/fixtures/invalid_access.json +11 -0
  23. myelectricaldatapy-2.2.2/tests/fixtures/tempo.json +1 -0
  24. {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/tests/test_analytics.py +41 -5
  25. {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/tests/test_load_data.py +22 -10
  26. myelectricaldatapy-2.2.0/myelectricaldatapy/auth.py +0 -84
  27. myelectricaldatapy-2.2.0/tests/__init__.py +0 -0
  28. myelectricaldatapy-2.2.0/tests/conftest.py +0 -46
  29. myelectricaldatapy-2.2.0/tests/consts.py +0 -1425
  30. {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/.flake8 +0 -0
  31. {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/.github/dependabot.yml +0 -0
  32. {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/.github/workflows/lint.yml +0 -0
  33. {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/.github/workflows/pythonpublish.yml +0 -0
  34. {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/.github/workflows/release.yml +0 -0
  35. {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/.vscode/settings.json +0 -0
  36. {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/LICENSE +0 -0
  37. {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/MANIFEST.in +0 -0
  38. {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/README.md +0 -0
  39. {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/myelectricaldatapy/__init__.py +0 -0
  40. {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/myelectricaldatapy/analytics.py +0 -0
  41. {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/myelectricaldatapy/exceptions.py +0 -0
  42. {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/myelectricaldatapy/py.typed +0 -0
  43. {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/myelectricaldatapy.egg-info/dependency_links.txt +0 -0
  44. {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/myelectricaldatapy.egg-info/requires.txt +0 -0
  45. {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/myelectricaldatapy.egg-info/top_level.txt +0 -0
  46. {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/requirements-dev.txt +0 -0
  47. {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/requirements.txt +0 -0
  48. {myelectricaldatapy-2.2.0 → myelectricaldatapy-2.2.2}/setup.cfg +0 -0
@@ -1,26 +1,21 @@
1
- name: Dependabot auto-approve
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
- dependabot:
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}}
@@ -110,5 +110,5 @@ dmypy.json
110
110
  #pyannotate
111
111
  type_info.json
112
112
 
113
- #perso
114
- test_home.py
113
+ # Secret files
114
+ secrets.yaml
@@ -1,14 +1,14 @@
1
1
  ---
2
2
  repos:
3
3
  - repo: https://github.com/astral-sh/ruff-pre-commit
4
- rev: v0.4.1
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.2.6
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.9.0
49
+ rev: v1.10.0
51
50
  hooks:
52
51
  - id: mypy
53
52
  args: [--strict, --ignore-missing-imports]
54
- additional_dependencies: [types-requests==2.28.11.7]
55
- exclude: tests/
53
+ exclude: (tests)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: myelectricaldatapy
3
- Version: 2.2.0
3
+ Version: 2.2.2
4
4
  Summary: Fetch Linky data from myelectricaldata.fr
5
5
  Author-email: Cyr-ius <cyr-ius@ipocus.net>
6
6
  License: GPL-3
@@ -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
- # Please , fill good values...
20
- PDL = "0123456789"
21
- TOKEN = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
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 main() -> None:
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
- await myPdl.async_update()
43
- print(myPdl.stats)
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
- loop = asyncio.get_event_loop()
47
- loop.run_until_complete(main())
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
+ )
@@ -9,6 +9,7 @@ ATTR_PRICES = "prices"
9
9
  ATTR_SERVICE = "service"
10
10
  ATTR_STANDARD = "standard"
11
11
  ATTR_START = "start"
12
+ ATTR_FN = "function"
12
13
  CONSUMPTION = "consumption"
13
14
  DAILY_CONSUM = "daily_consumption"
14
15
  DAILY_PROD = "daily_production"
@@ -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, token: str, session: Any | None = None, timeout: int = TIMEOUT
23
+ self,
24
+ token: str,
25
+ session: ClientSession = ClientSession(),
26
+ timeout: int = TIMEOUT,
22
27
  ) -> None:
23
28
  """Initialize."""
24
- self.auth = EnedisAuth(token, session, timeout)
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.auth.request(path=path)
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.request(path=f"rte/tempo/{str_start}/{str_end}")
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.auth.request(path=f"rte/ecowatt/{str_start}/{str_end}")
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
- data = None
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
- rsp = await self.async_fetch_datas(DETAIL_PROD, pdl, start, end)
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
- if (
182
- rsp is None
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], rsp)
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: Any | None = None,
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] = {"func": func, ATTR_START: dt_start, ATTR_END: dt_end}
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, _param in self._params.items():
320
+ for mode, attr in self._params.items():
319
321
  dataset = {}
320
- start = _param[ATTR_START]
321
- end = _param[ATTR_END]
322
- if func := _param.get("func"):
323
- try:
324
- dataset = await func(self.pdl, start, end)
325
- if dataset is None or dataset.get("meter_reading") is None:
326
- raise EnedisException("Data collect is empty")
327
- except EnedisException as error:
328
- checked = False
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) > 0:
332
- checked = checked and len(data) > 0
333
- self._params[mode].update({"data": data})
334
- if mode == CONSUMPTION and self._tempo_subs:
335
- self.tempo = await self._api.async_get_tempo(start, end)
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: myelectricaldatapy
3
- Version: 2.2.0
3
+ Version: 2.2.2
4
4
  Summary: Fetch Linky data from myelectricaldata.fr
5
5
  Author-email: Cyr-ius <cyr-ius@ipocus.net>
6
6
  License: GPL-3
@@ -9,7 +9,7 @@ pyproject.toml
9
9
  requirements-dev.txt
10
10
  requirements.txt
11
11
  .github/dependabot.yml
12
- .github/workflows/dependbot-auto-approve.yml
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.flake8-tidy-imports.banned-api]
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,9 @@
1
+ """Tests for myelectricaldata."""
2
+
3
+ from pathlib import Path
4
+
5
+
6
+ def load_fixture(filename: str) -> str:
7
+ """Load a fixture."""
8
+ path = Path(__package__) / "fixtures" / filename
9
+ return path.read_text(encoding="utf-8")
@@ -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()
@@ -0,0 +1,4 @@
1
+ """Test constants."""
2
+
3
+ PDL = "01234567890"
4
+ TOKEN = "xxxxxxxxxxxxx"
@@ -0,0 +1,10 @@
1
+ {
2
+ "valid": true,
3
+ "consent_expiration_date": "2026-01-14T15:32:38",
4
+ "call_number": 7,
5
+ "quota_reached": false,
6
+ "quota_limit": 50,
7
+ "quota_reset_at": "2023-03-01T23:59:59.999999",
8
+ "last_call": "2023-03-01T13:58:24.532501",
9
+ "ban": false
10
+ }