pygazpar 1.3.0b2__tar.gz → 1.3.0b3__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {pygazpar-1.3.0b2 → pygazpar-1.3.0b3}/CHANGELOG.md +5 -1
- {pygazpar-1.3.0b2 → pygazpar-1.3.0b3}/PKG-INFO +15 -7
- {pygazpar-1.3.0b2 → pygazpar-1.3.0b3}/README.md +14 -6
- {pygazpar-1.3.0b2 → pygazpar-1.3.0b3}/pygazpar/__main__.py +1 -1
- pygazpar-1.3.0b3/pygazpar/api_client.py +228 -0
- pygazpar-1.3.0b3/pygazpar/client.py +98 -0
- {pygazpar-1.3.0b2 → pygazpar-1.3.0b3}/pygazpar/datasource.py +128 -167
- {pygazpar-1.3.0b2 → pygazpar-1.3.0b3}/pygazpar/excelparser.py +7 -7
- {pygazpar-1.3.0b2 → pygazpar-1.3.0b3}/pygazpar/jsonparser.py +2 -2
- {pygazpar-1.3.0b2 → pygazpar-1.3.0b3}/pyproject.toml +1 -1
- pygazpar-1.3.0b2/pygazpar/client.py +0 -64
- {pygazpar-1.3.0b2 → pygazpar-1.3.0b3}/LICENSE +0 -0
- {pygazpar-1.3.0b2 → pygazpar-1.3.0b3}/pygazpar/__init__.py +0 -0
- {pygazpar-1.3.0b2 → pygazpar-1.3.0b3}/pygazpar/enum.py +0 -0
- {pygazpar-1.3.0b2 → pygazpar-1.3.0b3}/pygazpar/resources/daily_data_sample.json +0 -0
- {pygazpar-1.3.0b2 → pygazpar-1.3.0b3}/pygazpar/resources/hourly_data_sample.json +0 -0
- {pygazpar-1.3.0b2 → pygazpar-1.3.0b3}/pygazpar/resources/monthly_data_sample.json +0 -0
- {pygazpar-1.3.0b2 → pygazpar-1.3.0b3}/pygazpar/resources/weekly_data_sample.json +0 -0
- {pygazpar-1.3.0b2 → pygazpar-1.3.0b3}/pygazpar/resources/yearly_data_sample.json +0 -0
- {pygazpar-1.3.0b2 → pygazpar-1.3.0b3}/pygazpar/version.py +0 -0
@@ -4,7 +4,11 @@ All notable changes to this project will be documented in this file.
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
6
6
|
|
7
|
-
## [1.3.0] - 2025-02-
|
7
|
+
## [1.3.0] - 2025-02-09
|
8
|
+
|
9
|
+
### Added
|
10
|
+
|
11
|
+
[#84](https://github.com/ssenart/PyGazpar/issues/84) : Add a function to retrieve list of PCE ids and labels from account.
|
8
12
|
|
9
13
|
### Changed
|
10
14
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: pygazpar
|
3
|
-
Version: 1.3.
|
3
|
+
Version: 1.3.0b3
|
4
4
|
Summary: Python library to download gas consumption from a GrDF (French Gas Company) account
|
5
5
|
License: MIT License
|
6
6
|
|
@@ -113,8 +113,12 @@ client = pygazpar.Client(pygazpar.JsonWebDataSource(
|
|
113
113
|
password='your password')
|
114
114
|
)
|
115
115
|
|
116
|
-
|
117
|
-
|
116
|
+
# Returns the list of your PCE identifiers attached to your account.
|
117
|
+
pce_identifiers = client.get_pce_identifiers()
|
118
|
+
|
119
|
+
# Returns the daily and monthly consumptions for the last 60 days on your PCE identifier.
|
120
|
+
data = client.load_since(pce_identifier='your PCE identifier',
|
121
|
+
last_n_days=60,
|
118
122
|
frequencies=[pygazpar.Frequency.DAILY, pygazpar.Frequency.MONTHLY])
|
119
123
|
```
|
120
124
|
See [samples/jsonSample.py](samples/jsonSample.py) file for the full example.
|
@@ -129,8 +133,12 @@ client = pygazpar.Client(pygazpar.ExcelWebDataSource(
|
|
129
133
|
password='your password')
|
130
134
|
)
|
131
135
|
|
132
|
-
|
133
|
-
|
136
|
+
# Returns the list of your PCE identifiers attached to your account.
|
137
|
+
pce_identifiers = client.get_pce_identifiers()
|
138
|
+
|
139
|
+
# Returns the daily and monthly consumptions for the last 60 days on your PCE identifier.
|
140
|
+
data = client.load_since(pce_identifier='your PCE identifier',
|
141
|
+
last_n_days=60,
|
134
142
|
frequencies=[pygazpar.Frequency.DAILY, pygazpar.Frequency.MONTHLY])
|
135
143
|
```
|
136
144
|
See [samples/excelSample.py](samples/jsonSample.py) file for the full example.
|
@@ -142,8 +150,8 @@ import pygazpar
|
|
142
150
|
|
143
151
|
client = pygazpar.Client(pygazpar.TestDataSource())
|
144
152
|
|
145
|
-
data = client.
|
146
|
-
|
153
|
+
data = client.load_since(pce_identifier='your PCE identifier',
|
154
|
+
last_n_days=10,
|
147
155
|
frequencies=[pygazpar.Frequency.DAILY, Frequency.MONTHLY])
|
148
156
|
```
|
149
157
|
See [samples/testSample.py](samples/jsonSample.py) file for the full example.
|
@@ -76,8 +76,12 @@ client = pygazpar.Client(pygazpar.JsonWebDataSource(
|
|
76
76
|
password='your password')
|
77
77
|
)
|
78
78
|
|
79
|
-
|
80
|
-
|
79
|
+
# Returns the list of your PCE identifiers attached to your account.
|
80
|
+
pce_identifiers = client.get_pce_identifiers()
|
81
|
+
|
82
|
+
# Returns the daily and monthly consumptions for the last 60 days on your PCE identifier.
|
83
|
+
data = client.load_since(pce_identifier='your PCE identifier',
|
84
|
+
last_n_days=60,
|
81
85
|
frequencies=[pygazpar.Frequency.DAILY, pygazpar.Frequency.MONTHLY])
|
82
86
|
```
|
83
87
|
See [samples/jsonSample.py](samples/jsonSample.py) file for the full example.
|
@@ -92,8 +96,12 @@ client = pygazpar.Client(pygazpar.ExcelWebDataSource(
|
|
92
96
|
password='your password')
|
93
97
|
)
|
94
98
|
|
95
|
-
|
96
|
-
|
99
|
+
# Returns the list of your PCE identifiers attached to your account.
|
100
|
+
pce_identifiers = client.get_pce_identifiers()
|
101
|
+
|
102
|
+
# Returns the daily and monthly consumptions for the last 60 days on your PCE identifier.
|
103
|
+
data = client.load_since(pce_identifier='your PCE identifier',
|
104
|
+
last_n_days=60,
|
97
105
|
frequencies=[pygazpar.Frequency.DAILY, pygazpar.Frequency.MONTHLY])
|
98
106
|
```
|
99
107
|
See [samples/excelSample.py](samples/jsonSample.py) file for the full example.
|
@@ -105,8 +113,8 @@ import pygazpar
|
|
105
113
|
|
106
114
|
client = pygazpar.Client(pygazpar.TestDataSource())
|
107
115
|
|
108
|
-
data = client.
|
109
|
-
|
116
|
+
data = client.load_since(pce_identifier='your PCE identifier',
|
117
|
+
last_n_days=10,
|
110
118
|
frequencies=[pygazpar.Frequency.DAILY, Frequency.MONTHLY])
|
111
119
|
```
|
112
120
|
See [samples/testSample.py](samples/jsonSample.py) file for the full example.
|
@@ -67,7 +67,7 @@ def main():
|
|
67
67
|
raise ValueError("Invalid datasource: (json | excel | test) is expected")
|
68
68
|
|
69
69
|
try:
|
70
|
-
data = client.
|
70
|
+
data = client.load_since(args.pce, int(args.lastNDays), [args.frequency])
|
71
71
|
except BaseException: # pylint: disable=broad-except
|
72
72
|
print("An error occured while querying PyGazpar library : %s", traceback.format_exc())
|
73
73
|
return 1
|
@@ -0,0 +1,228 @@
|
|
1
|
+
import http.cookiejar
|
2
|
+
import json
|
3
|
+
import logging
|
4
|
+
import time
|
5
|
+
import traceback
|
6
|
+
from datetime import date
|
7
|
+
from enum import Enum
|
8
|
+
from typing import Any
|
9
|
+
|
10
|
+
from requests import Response, Session
|
11
|
+
|
12
|
+
SESSION_TOKEN_URL = "https://connexion.grdf.fr/api/v1/authn"
|
13
|
+
SESSION_TOKEN_PAYLOAD = """{{
|
14
|
+
"username": "{0}",
|
15
|
+
"password": "{1}",
|
16
|
+
"options": {{
|
17
|
+
"multiOptionalFactorEnroll": "false",
|
18
|
+
"warnBeforePasswordExpired": "false"
|
19
|
+
}}
|
20
|
+
}}"""
|
21
|
+
|
22
|
+
AUTH_TOKEN_URL = "https://connexion.grdf.fr/login/sessionCookieRedirect"
|
23
|
+
AUTH_TOKEN_PARAMS = """{{
|
24
|
+
"checkAccountSetupComplete": "true",
|
25
|
+
"token": "{0}",
|
26
|
+
"redirectUrl": "https://monespace.grdf.fr"
|
27
|
+
}}"""
|
28
|
+
|
29
|
+
API_BASE_URL = "https://monespace.grdf.fr/api"
|
30
|
+
|
31
|
+
DATE_FORMAT = "%Y-%m-%d"
|
32
|
+
|
33
|
+
Logger = logging.getLogger(__name__)
|
34
|
+
|
35
|
+
|
36
|
+
# ------------------------------------------------------
|
37
|
+
class ConsumptionType(str, Enum):
|
38
|
+
INFORMATIVE = "informatives"
|
39
|
+
PUBLISHED = "publiees"
|
40
|
+
|
41
|
+
|
42
|
+
# ------------------------------------------------------
|
43
|
+
class Frequency(str, Enum):
|
44
|
+
HOURLY = "Horaire"
|
45
|
+
DAILY = "Journalier"
|
46
|
+
WEEKLY = "Hebdomadaire"
|
47
|
+
MONTHLY = "Mensuel"
|
48
|
+
YEARLY = "Annuel"
|
49
|
+
|
50
|
+
|
51
|
+
# ------------------------------------------------------
|
52
|
+
class ServerError(SystemError):
|
53
|
+
|
54
|
+
def __init__(self, message: str, status_code: int):
|
55
|
+
super().__init__(message)
|
56
|
+
self.status_code = status_code
|
57
|
+
|
58
|
+
|
59
|
+
# ------------------------------------------------------
|
60
|
+
class InternalServerError(ServerError):
|
61
|
+
|
62
|
+
def __init__(self, message: str):
|
63
|
+
super().__init__(message, 500)
|
64
|
+
|
65
|
+
|
66
|
+
# ------------------------------------------------------
|
67
|
+
class APIClient:
|
68
|
+
|
69
|
+
# ------------------------------------------------------
|
70
|
+
def __init__(self, username: str, password: str, retry_count: int = 10):
|
71
|
+
self._username = username
|
72
|
+
self._password = password
|
73
|
+
self._retry_count = retry_count
|
74
|
+
self._session = None
|
75
|
+
|
76
|
+
# ------------------------------------------------------
|
77
|
+
def login(self):
|
78
|
+
if self._session is not None:
|
79
|
+
return
|
80
|
+
|
81
|
+
session = Session()
|
82
|
+
session.headers.update({"domain": "grdf.fr"})
|
83
|
+
session.headers.update({"Content-Type": "application/json"})
|
84
|
+
session.headers.update({"X-Requested-With": "XMLHttpRequest"})
|
85
|
+
|
86
|
+
payload = SESSION_TOKEN_PAYLOAD.format(self._username, self._password)
|
87
|
+
|
88
|
+
response = session.post(SESSION_TOKEN_URL, data=payload)
|
89
|
+
|
90
|
+
if response.status_code != 200:
|
91
|
+
raise ServerError(
|
92
|
+
f"An error occurred while logging in. Status code: {response.status_code} - {response.text}",
|
93
|
+
response.status_code,
|
94
|
+
)
|
95
|
+
|
96
|
+
session_token = response.json().get("sessionToken")
|
97
|
+
|
98
|
+
jar = http.cookiejar.CookieJar()
|
99
|
+
|
100
|
+
self._session = Session() # pylint: disable=attribute-defined-outside-init
|
101
|
+
self._session.headers.update({"Content-Type": "application/json"})
|
102
|
+
self._session.headers.update({"X-Requested-With": "XMLHttpRequest"})
|
103
|
+
|
104
|
+
params = json.loads(AUTH_TOKEN_PARAMS.format(session_token))
|
105
|
+
|
106
|
+
response = self._session.get(AUTH_TOKEN_URL, params=params, allow_redirects=True, cookies=jar) # type: ignore
|
107
|
+
|
108
|
+
if response.status_code != 200:
|
109
|
+
raise ServerError(
|
110
|
+
f"An error occurred while getting the auth token. Status code: {response.status_code} - {response.text}",
|
111
|
+
response.status_code,
|
112
|
+
)
|
113
|
+
|
114
|
+
# ------------------------------------------------------
|
115
|
+
def is_logged_in(self) -> bool:
|
116
|
+
return self._session is not None
|
117
|
+
|
118
|
+
# ------------------------------------------------------
|
119
|
+
def logout(self):
|
120
|
+
if self._session is None:
|
121
|
+
return
|
122
|
+
|
123
|
+
self._session.close()
|
124
|
+
self._session = None
|
125
|
+
|
126
|
+
# ------------------------------------------------------
|
127
|
+
def get(self, endpoint: str, params: dict[str, Any]) -> Response:
|
128
|
+
|
129
|
+
if self._session is None:
|
130
|
+
raise ConnectionError("You must login first")
|
131
|
+
|
132
|
+
retry = self._retry_count
|
133
|
+
while retry > 0:
|
134
|
+
|
135
|
+
try:
|
136
|
+
response = self._session.get(f"{API_BASE_URL}{endpoint}", params=params)
|
137
|
+
|
138
|
+
if "text/html" in response.headers.get("Content-Type"): # type: ignore
|
139
|
+
raise InternalServerError(
|
140
|
+
f"An unknown error occurred. Please check your query parameters (endpoint: {endpoint}): {params}"
|
141
|
+
)
|
142
|
+
|
143
|
+
if response.status_code != 200:
|
144
|
+
raise ServerError(
|
145
|
+
f"HTTP error on enpoint '{endpoint}': Status code: {response.status_code} - {response.text}. Query parameters: {params}",
|
146
|
+
response.status_code,
|
147
|
+
)
|
148
|
+
|
149
|
+
break
|
150
|
+
except InternalServerError as internalServerError: # pylint: disable=broad-exception-caught
|
151
|
+
if retry == 1:
|
152
|
+
Logger.error(f"{internalServerError}. Retry limit reached: {traceback.format_exc()}")
|
153
|
+
raise internalServerError
|
154
|
+
retry -= 1
|
155
|
+
Logger.warning(f"{internalServerError}. Retry in 3 seconds ({retry} retries left)...")
|
156
|
+
time.sleep(3)
|
157
|
+
|
158
|
+
return response
|
159
|
+
|
160
|
+
# ------------------------------------------------------
|
161
|
+
def get_pce_list(self, details: bool = False) -> list[Any]:
|
162
|
+
|
163
|
+
res = self.get("/e-conso/pce", {"details": details}).json()
|
164
|
+
|
165
|
+
if type(res) is not list:
|
166
|
+
raise TypeError(f"Invalid response type: {type(res)} (list expected)")
|
167
|
+
|
168
|
+
return res
|
169
|
+
|
170
|
+
# ------------------------------------------------------
|
171
|
+
def get_pce_consumption(
|
172
|
+
self, consumption_type: ConsumptionType, start_date: date, end_date: date, pce_list: list[str]
|
173
|
+
) -> dict[str, Any]:
|
174
|
+
|
175
|
+
start = start_date.strftime(DATE_FORMAT)
|
176
|
+
end = end_date.strftime(DATE_FORMAT)
|
177
|
+
|
178
|
+
res = self.get(
|
179
|
+
f"/e-conso/pce/consommation/{consumption_type.value}",
|
180
|
+
{"dateDebut": start, "dateFin": end, "pceList[]": ",".join(pce_list)},
|
181
|
+
).json()
|
182
|
+
|
183
|
+
if type(res) is list and len(res) == 0:
|
184
|
+
return dict[str, Any]()
|
185
|
+
|
186
|
+
if type(res) is not dict:
|
187
|
+
raise TypeError(f"Invalid response type: {type(res)} (dict expected)")
|
188
|
+
|
189
|
+
return res
|
190
|
+
|
191
|
+
# ------------------------------------------------------
|
192
|
+
def get_pce_consumption_excelsheet(
|
193
|
+
self,
|
194
|
+
consumption_type: ConsumptionType,
|
195
|
+
start_date: date,
|
196
|
+
end_date: date,
|
197
|
+
frequency: Frequency,
|
198
|
+
pce_list: list[str],
|
199
|
+
) -> dict[str, Any]:
|
200
|
+
|
201
|
+
start = start_date.strftime(DATE_FORMAT)
|
202
|
+
end = end_date.strftime(DATE_FORMAT)
|
203
|
+
|
204
|
+
response = self.get(
|
205
|
+
f"/e-conso/pce/consommation/{consumption_type.value}/telecharger",
|
206
|
+
{"dateDebut": start, "dateFin": end, "frequence": frequency.value, "pceList[]": ",".join(pce_list)},
|
207
|
+
)
|
208
|
+
|
209
|
+
filename = response.headers["Content-Disposition"].split("filename=")[1]
|
210
|
+
|
211
|
+
res = {"filename": filename, "content": response.content}
|
212
|
+
|
213
|
+
return res
|
214
|
+
|
215
|
+
# ------------------------------------------------------
|
216
|
+
def get_pce_meteo(self, end_date: date, days: int, pce: str) -> dict[str, Any]:
|
217
|
+
|
218
|
+
end = end_date.strftime(DATE_FORMAT)
|
219
|
+
|
220
|
+
res = self.get(f"/e-conso/pce/{pce}/meteo", {"dateFinPeriode": end, "nbJours": days}).json()
|
221
|
+
|
222
|
+
if type(res) is list and len(res) == 0:
|
223
|
+
return dict[str, Any]()
|
224
|
+
|
225
|
+
if type(res) is not dict:
|
226
|
+
raise TypeError(f"Invalid response type: {type(res)} (dict expected)")
|
227
|
+
|
228
|
+
return res
|
@@ -0,0 +1,98 @@
|
|
1
|
+
import logging
|
2
|
+
import warnings
|
3
|
+
from datetime import date, timedelta
|
4
|
+
from typing import Optional
|
5
|
+
|
6
|
+
from pygazpar.datasource import IDataSource, MeterReadingsByFrequency
|
7
|
+
from pygazpar.enum import Frequency
|
8
|
+
|
9
|
+
DEFAULT_LAST_N_DAYS = 365
|
10
|
+
|
11
|
+
|
12
|
+
Logger = logging.getLogger(__name__)
|
13
|
+
|
14
|
+
|
15
|
+
# ------------------------------------------------------------------------------------------------------------
|
16
|
+
class Client:
|
17
|
+
|
18
|
+
# ------------------------------------------------------
|
19
|
+
def __init__(self, dataSource: IDataSource):
|
20
|
+
self.__dataSource = dataSource
|
21
|
+
|
22
|
+
# ------------------------------------------------------
|
23
|
+
def login(self):
|
24
|
+
|
25
|
+
try:
|
26
|
+
self.__dataSource.login()
|
27
|
+
except Exception:
|
28
|
+
Logger.error("An unexpected error occured while login", exc_info=True)
|
29
|
+
raise
|
30
|
+
|
31
|
+
# ------------------------------------------------------
|
32
|
+
def logout(self):
|
33
|
+
|
34
|
+
try:
|
35
|
+
self.__dataSource.logout()
|
36
|
+
except Exception:
|
37
|
+
Logger.error("An unexpected error occured while logout", exc_info=True)
|
38
|
+
raise
|
39
|
+
|
40
|
+
# ------------------------------------------------------
|
41
|
+
def get_pce_identifiers(self) -> list[str]:
|
42
|
+
|
43
|
+
try:
|
44
|
+
res = self.__dataSource.get_pce_identifiers()
|
45
|
+
except Exception:
|
46
|
+
Logger.error("An unexpected error occured while getting the PCE identifiers", exc_info=True)
|
47
|
+
raise
|
48
|
+
|
49
|
+
return res
|
50
|
+
|
51
|
+
# ------------------------------------------------------
|
52
|
+
def load_since(
|
53
|
+
self, pce_identifier: str, last_n_days: int = DEFAULT_LAST_N_DAYS, frequencies: Optional[list[Frequency]] = None
|
54
|
+
) -> MeterReadingsByFrequency:
|
55
|
+
|
56
|
+
end_date = date.today()
|
57
|
+
start_date = end_date + timedelta(days=-last_n_days)
|
58
|
+
|
59
|
+
res = self.load_date_range(pce_identifier, start_date, end_date, frequencies)
|
60
|
+
|
61
|
+
return res
|
62
|
+
|
63
|
+
# ------------------------------------------------------
|
64
|
+
def load_date_range(
|
65
|
+
self, pce_identifier: str, start_date: date, end_date: date, frequencies: Optional[list[Frequency]] = None
|
66
|
+
) -> MeterReadingsByFrequency:
|
67
|
+
|
68
|
+
Logger.debug("Start loading the data...")
|
69
|
+
|
70
|
+
try:
|
71
|
+
res = self.__dataSource.load(pce_identifier, start_date, end_date, frequencies)
|
72
|
+
|
73
|
+
Logger.debug("The data load terminates normally")
|
74
|
+
except Exception:
|
75
|
+
Logger.error("An unexpected error occured while loading the data", exc_info=True)
|
76
|
+
raise
|
77
|
+
|
78
|
+
return res
|
79
|
+
|
80
|
+
# ------------------------------------------------------
|
81
|
+
def loadSince(
|
82
|
+
self, pceIdentifier: str, lastNDays: int = DEFAULT_LAST_N_DAYS, frequencies: Optional[list[Frequency]] = None
|
83
|
+
) -> MeterReadingsByFrequency:
|
84
|
+
warnings.warn(
|
85
|
+
"Client.loadSince() method will be removed in 2026-01-01. Please migrate to Client.load_since() method",
|
86
|
+
DeprecationWarning,
|
87
|
+
)
|
88
|
+
return self.load_since(pceIdentifier, lastNDays, frequencies)
|
89
|
+
|
90
|
+
# ------------------------------------------------------
|
91
|
+
def loadDateRange(
|
92
|
+
self, pceIdentifier: str, startDate: date, endDate: date, frequencies: Optional[list[Frequency]] = None
|
93
|
+
) -> MeterReadingsByFrequency:
|
94
|
+
warnings.warn(
|
95
|
+
"Client.loadDateRange() method will be removed in 2026-01-01. Please migrate to Client.load_date_range() method",
|
96
|
+
DeprecationWarning,
|
97
|
+
)
|
98
|
+
return self.load_date_range(pceIdentifier, startDate, endDate, frequencies)
|
@@ -1,52 +1,46 @@
|
|
1
1
|
import glob
|
2
|
-
import http.cookiejar
|
3
2
|
import json
|
4
3
|
import logging
|
5
4
|
import os
|
6
|
-
import time
|
7
5
|
from abc import ABC, abstractmethod
|
8
6
|
from datetime import date, timedelta
|
9
|
-
from typing import Any,
|
7
|
+
from typing import Any, Optional, cast
|
10
8
|
|
11
9
|
import pandas as pd
|
12
|
-
from requests import Session
|
13
10
|
|
11
|
+
from pygazpar.api_client import APIClient, ConsumptionType
|
12
|
+
from pygazpar.api_client import Frequency as APIClientFrequency
|
14
13
|
from pygazpar.enum import Frequency, PropertyName
|
15
14
|
from pygazpar.excelparser import ExcelParser
|
16
15
|
from pygazpar.jsonparser import JsonParser
|
17
16
|
|
18
|
-
SESSION_TOKEN_URL = "https://connexion.grdf.fr/api/v1/authn"
|
19
|
-
SESSION_TOKEN_PAYLOAD = """{{
|
20
|
-
"username": "{0}",
|
21
|
-
"password": "{1}",
|
22
|
-
"options": {{
|
23
|
-
"multiOptionalFactorEnroll": "false",
|
24
|
-
"warnBeforePasswordExpired": "false"
|
25
|
-
}}
|
26
|
-
}}"""
|
27
|
-
|
28
|
-
AUTH_TOKEN_URL = "https://connexion.grdf.fr/login/sessionCookieRedirect"
|
29
|
-
AUTH_TOKEN_PARAMS = """{{
|
30
|
-
"checkAccountSetupComplete": "true",
|
31
|
-
"token": "{0}",
|
32
|
-
"redirectUrl": "https://monespace.grdf.fr"
|
33
|
-
}}"""
|
34
|
-
|
35
17
|
Logger = logging.getLogger(__name__)
|
36
18
|
|
37
|
-
MeterReading =
|
19
|
+
MeterReading = dict[str, Any]
|
38
20
|
|
39
|
-
MeterReadings =
|
21
|
+
MeterReadings = list[MeterReading]
|
40
22
|
|
41
|
-
MeterReadingsByFrequency =
|
23
|
+
MeterReadingsByFrequency = dict[str, MeterReadings]
|
42
24
|
|
43
25
|
|
44
26
|
# ------------------------------------------------------------------------------------------------------------
|
45
27
|
class IDataSource(ABC): # pylint: disable=too-few-public-methods
|
46
28
|
|
29
|
+
@abstractmethod
|
30
|
+
def login(self):
|
31
|
+
pass
|
32
|
+
|
33
|
+
@abstractmethod
|
34
|
+
def logout(self):
|
35
|
+
pass
|
36
|
+
|
37
|
+
@abstractmethod
|
38
|
+
def get_pce_identifiers(self) -> list[str]:
|
39
|
+
pass
|
40
|
+
|
47
41
|
@abstractmethod
|
48
42
|
def load(
|
49
|
-
self, pceIdentifier: str, startDate: date, endDate: date, frequencies: Optional[
|
43
|
+
self, pceIdentifier: str, startDate: date, endDate: date, frequencies: Optional[list[Frequency]] = None
|
50
44
|
) -> MeterReadingsByFrequency:
|
51
45
|
pass
|
52
46
|
|
@@ -57,65 +51,50 @@ class WebDataSource(IDataSource): # pylint: disable=too-few-public-methods
|
|
57
51
|
# ------------------------------------------------------
|
58
52
|
def __init__(self, username: str, password: str):
|
59
53
|
|
60
|
-
self.
|
61
|
-
self.__password = password
|
54
|
+
self._api_client = APIClient(username, password)
|
62
55
|
|
63
56
|
# ------------------------------------------------------
|
64
|
-
def
|
65
|
-
self, pceIdentifier: str, startDate: date, endDate: date, frequencies: Optional[List[Frequency]] = None
|
66
|
-
) -> MeterReadingsByFrequency:
|
57
|
+
def login(self):
|
67
58
|
|
68
|
-
self.
|
69
|
-
|
70
|
-
res = self._loadFromSession(pceIdentifier, startDate, endDate, frequencies)
|
71
|
-
|
72
|
-
Logger.debug("The data update terminates normally")
|
73
|
-
|
74
|
-
return res
|
59
|
+
if not self._api_client.is_logged_in():
|
60
|
+
self._api_client.login()
|
75
61
|
|
76
62
|
# ------------------------------------------------------
|
77
|
-
def
|
63
|
+
def logout(self):
|
78
64
|
|
79
|
-
|
80
|
-
|
81
|
-
session.headers.update({"Content-Type": "application/json"})
|
82
|
-
session.headers.update({"X-Requested-With": "XMLHttpRequest"})
|
65
|
+
if self._api_client.is_logged_in():
|
66
|
+
self._api_client.logout()
|
83
67
|
|
84
|
-
|
85
|
-
|
86
|
-
response = session.post(SESSION_TOKEN_URL, data=payload)
|
87
|
-
|
88
|
-
if response.status_code != 200:
|
89
|
-
raise ValueError(
|
90
|
-
f"An error occurred while logging in. Status code: {response.status_code} - {response.text}"
|
91
|
-
)
|
68
|
+
# ------------------------------------------------------
|
69
|
+
def get_pce_identifiers(self) -> list[str]:
|
92
70
|
|
93
|
-
|
71
|
+
if not self._api_client.is_logged_in():
|
72
|
+
self._api_client.login()
|
94
73
|
|
95
|
-
|
74
|
+
pce_list = self._api_client.get_pce_list()
|
96
75
|
|
97
|
-
|
76
|
+
if pce_list is None:
|
77
|
+
return []
|
98
78
|
|
99
|
-
|
100
|
-
self._session.headers.update({"Content-Type": "application/json"})
|
101
|
-
self._session.headers.update({"X-Requested-With": "XMLHttpRequest"})
|
79
|
+
return [pce["idObject"] for pce in pce_list]
|
102
80
|
|
103
|
-
|
81
|
+
# ------------------------------------------------------
|
82
|
+
def load(
|
83
|
+
self, pceIdentifier: str, startDate: date, endDate: date, frequencies: Optional[list[Frequency]] = None
|
84
|
+
) -> MeterReadingsByFrequency:
|
104
85
|
|
105
|
-
|
86
|
+
if not self._api_client.is_logged_in():
|
87
|
+
self._api_client.login()
|
106
88
|
|
107
|
-
|
108
|
-
raise ValueError(
|
109
|
-
f"An error occurred while getting the auth token. Status code: {response.status_code} - {response.text}"
|
110
|
-
)
|
89
|
+
res = self._loadFromSession(pceIdentifier, startDate, endDate, frequencies)
|
111
90
|
|
112
|
-
|
91
|
+
Logger.debug("The data update terminates normally")
|
113
92
|
|
114
|
-
return
|
93
|
+
return res
|
115
94
|
|
116
95
|
@abstractmethod
|
117
96
|
def _loadFromSession(
|
118
|
-
self, pceIdentifier: str, startDate: date, endDate: date, frequencies: Optional[
|
97
|
+
self, pceIdentifier: str, startDate: date, endDate: date, frequencies: Optional[list[Frequency]] = None
|
119
98
|
) -> MeterReadingsByFrequency:
|
120
99
|
pass
|
121
100
|
|
@@ -123,8 +102,6 @@ class WebDataSource(IDataSource): # pylint: disable=too-few-public-methods
|
|
123
102
|
# ------------------------------------------------------------------------------------------------------------
|
124
103
|
class ExcelWebDataSource(WebDataSource): # pylint: disable=too-few-public-methods
|
125
104
|
|
126
|
-
DATA_URL = "https://monespace.grdf.fr/api/e-conso/pce/consommation/informatives/telecharger?dateDebut={0}&dateFin={1}&frequence={3}&pceList[]={2}"
|
127
|
-
|
128
105
|
DATE_FORMAT = "%Y-%m-%d"
|
129
106
|
|
130
107
|
FREQUENCY_VALUES = {
|
@@ -146,7 +123,7 @@ class ExcelWebDataSource(WebDataSource): # pylint: disable=too-few-public-metho
|
|
146
123
|
|
147
124
|
# ------------------------------------------------------
|
148
125
|
def _loadFromSession( # pylint: disable=too-many-branches
|
149
|
-
self, pceIdentifier: str, startDate: date, endDate: date, frequencies: Optional[
|
126
|
+
self, pceIdentifier: str, startDate: date, endDate: date, frequencies: Optional[list[Frequency]] = None
|
150
127
|
) -> MeterReadingsByFrequency: # pylint: disable=too-many-branches
|
151
128
|
|
152
129
|
res = {}
|
@@ -171,33 +148,24 @@ class ExcelWebDataSource(WebDataSource): # pylint: disable=too-few-public-metho
|
|
171
148
|
frequencyList = list(set(frequencies))
|
172
149
|
|
173
150
|
for frequency in frequencyList:
|
174
|
-
# Inject parameters.
|
175
|
-
downloadUrl = ExcelWebDataSource.DATA_URL.format(
|
176
|
-
startDate.strftime(ExcelWebDataSource.DATE_FORMAT),
|
177
|
-
endDate.strftime(ExcelWebDataSource.DATE_FORMAT),
|
178
|
-
pceIdentifier,
|
179
|
-
ExcelWebDataSource.FREQUENCY_VALUES[frequency],
|
180
|
-
)
|
181
151
|
|
182
152
|
Logger.debug(
|
183
153
|
f"Loading data of frequency {ExcelWebDataSource.FREQUENCY_VALUES[frequency]} from {startDate.strftime(ExcelWebDataSource.DATE_FORMAT)} to {endDate.strftime(ExcelWebDataSource.DATE_FORMAT)}"
|
184
154
|
)
|
185
155
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
except Exception as e: # pylint: disable=broad-exception-caught
|
156
|
+
response = self._api_client.get_pce_consumption_excelsheet(
|
157
|
+
ConsumptionType.INFORMATIVE,
|
158
|
+
startDate,
|
159
|
+
endDate,
|
160
|
+
APIClientFrequency(ExcelWebDataSource.FREQUENCY_VALUES[frequency]),
|
161
|
+
[pceIdentifier],
|
162
|
+
)
|
194
163
|
|
195
|
-
|
196
|
-
|
164
|
+
filename = response["filename"]
|
165
|
+
content = response["content"]
|
197
166
|
|
198
|
-
|
199
|
-
|
200
|
-
retry -= 1
|
167
|
+
with open(f"{self.__tmpDirectory}/{filename}", "wb") as file:
|
168
|
+
file.write(content)
|
201
169
|
|
202
170
|
# Load the XLSX file into the data structure
|
203
171
|
file_list = glob.glob(data_file_path_pattern)
|
@@ -221,26 +189,6 @@ class ExcelWebDataSource(WebDataSource): # pylint: disable=too-few-public-metho
|
|
221
189
|
|
222
190
|
return res
|
223
191
|
|
224
|
-
# ------------------------------------------------------
|
225
|
-
def __downloadFile(self, session: Session, url: str, path: str):
|
226
|
-
|
227
|
-
response = session.get(url)
|
228
|
-
|
229
|
-
if "text/html" in response.headers.get("Content-Type"): # type: ignore
|
230
|
-
raise ValueError("An error occurred while loading data. Please check your credentials.")
|
231
|
-
|
232
|
-
if response.status_code != 200:
|
233
|
-
raise ValueError(
|
234
|
-
f"An error occurred while loading data. Status code: {response.status_code} - {response.text}"
|
235
|
-
)
|
236
|
-
|
237
|
-
response.raise_for_status()
|
238
|
-
|
239
|
-
filename = response.headers["Content-Disposition"].split("filename=")[1]
|
240
|
-
|
241
|
-
with open(f"{path}/{filename}", "wb") as file:
|
242
|
-
file.write(response.content)
|
243
|
-
|
244
192
|
|
245
193
|
# ------------------------------------------------------------------------------------------------------------
|
246
194
|
class ExcelFileDataSource(IDataSource): # pylint: disable=too-few-public-methods
|
@@ -249,8 +197,22 @@ class ExcelFileDataSource(IDataSource): # pylint: disable=too-few-public-method
|
|
249
197
|
|
250
198
|
self.__excelFile = excelFile
|
251
199
|
|
200
|
+
# ------------------------------------------------------
|
201
|
+
def login(self):
|
202
|
+
pass
|
203
|
+
|
204
|
+
# ------------------------------------------------------
|
205
|
+
def logout(self):
|
206
|
+
pass
|
207
|
+
|
208
|
+
# ------------------------------------------------------
|
209
|
+
def get_pce_identifiers(self) -> list[str]:
|
210
|
+
|
211
|
+
return ["0123456789"]
|
212
|
+
|
213
|
+
# ------------------------------------------------------
|
252
214
|
def load(
|
253
|
-
self, pceIdentifier: str, startDate: date, endDate: date, frequencies: Optional[
|
215
|
+
self, pceIdentifier: str, startDate: date, endDate: date, frequencies: Optional[list[Frequency]] = None
|
254
216
|
) -> MeterReadingsByFrequency:
|
255
217
|
|
256
218
|
res = {}
|
@@ -275,21 +237,16 @@ class ExcelFileDataSource(IDataSource): # pylint: disable=too-few-public-method
|
|
275
237
|
# ------------------------------------------------------------------------------------------------------------
|
276
238
|
class JsonWebDataSource(WebDataSource): # pylint: disable=too-few-public-methods
|
277
239
|
|
278
|
-
DATA_URL = (
|
279
|
-
"https://monespace.grdf.fr/api/e-conso/pce/consommation/informatives?dateDebut={0}&dateFin={1}&pceList[]={2}"
|
280
|
-
)
|
281
|
-
|
282
|
-
TEMPERATURES_URL = "https://monespace.grdf.fr/api/e-conso/pce/{0}/meteo?dateFinPeriode={1}&nbJours={2}"
|
283
|
-
|
284
240
|
INPUT_DATE_FORMAT = "%Y-%m-%d"
|
285
241
|
|
286
242
|
OUTPUT_DATE_FORMAT = "%d/%m/%Y"
|
287
243
|
|
244
|
+
# ------------------------------------------------------
|
288
245
|
def _loadFromSession(
|
289
|
-
self, pceIdentifier: str, startDate: date, endDate: date, frequencies: Optional[
|
246
|
+
self, pceIdentifier: str, startDate: date, endDate: date, frequencies: Optional[list[Frequency]] = None
|
290
247
|
) -> MeterReadingsByFrequency:
|
291
248
|
|
292
|
-
res =
|
249
|
+
res = dict[str, Any]()
|
293
250
|
|
294
251
|
computeByFrequency = {
|
295
252
|
Frequency.HOURLY: FrequencyConverter.computeHourly,
|
@@ -299,56 +256,30 @@ class JsonWebDataSource(WebDataSource): # pylint: disable=too-few-public-method
|
|
299
256
|
Frequency.YEARLY: FrequencyConverter.computeYearly,
|
300
257
|
}
|
301
258
|
|
302
|
-
|
303
|
-
downloadUrl = JsonWebDataSource.DATA_URL.format(
|
304
|
-
startDate.strftime(JsonWebDataSource.INPUT_DATE_FORMAT),
|
305
|
-
endDate.strftime(JsonWebDataSource.INPUT_DATE_FORMAT),
|
306
|
-
pceIdentifier,
|
307
|
-
)
|
308
|
-
|
309
|
-
# Retry mechanism.
|
310
|
-
retry = 10
|
311
|
-
while retry > 0:
|
312
|
-
|
313
|
-
try:
|
314
|
-
response = self._session.get(downloadUrl)
|
315
|
-
|
316
|
-
if "text/html" in response.headers.get("Content-Type"): # type: ignore
|
317
|
-
raise ValueError("An error occurred while loading data. Please check your credentials.")
|
318
|
-
|
319
|
-
if response.status_code != 200:
|
320
|
-
raise ValueError(
|
321
|
-
f"An error occurred while loading data. Status code: {response.status_code} - {response.text}"
|
322
|
-
)
|
323
|
-
|
324
|
-
break
|
325
|
-
except Exception as e: # pylint: disable=broad-exception-caught
|
326
|
-
|
327
|
-
if retry == 1:
|
328
|
-
raise e
|
329
|
-
|
330
|
-
Logger.error("An error occurred while loading data. Retry in 3 seconds.")
|
331
|
-
time.sleep(3)
|
332
|
-
retry -= 1
|
333
|
-
|
334
|
-
data = response.text
|
259
|
+
data = self._api_client.get_pce_consumption(ConsumptionType.INFORMATIVE, startDate, endDate, [pceIdentifier])
|
335
260
|
|
336
261
|
Logger.debug("Json meter data: %s", data)
|
337
262
|
|
338
263
|
# Temperatures URL: Inject parameters.
|
339
264
|
endDate = date.today() - timedelta(days=1) if endDate >= date.today() else endDate
|
340
|
-
days =
|
341
|
-
|
342
|
-
|
343
|
-
)
|
265
|
+
days = max(
|
266
|
+
min((endDate - startDate).days, 730), 10
|
267
|
+
) # At least 10 days, at most 730 days, to avoid HTTP 500 error.
|
344
268
|
|
345
269
|
# Get weather data.
|
346
|
-
|
270
|
+
try:
|
271
|
+
temperatures = self._api_client.get_pce_meteo(endDate, days, pceIdentifier)
|
272
|
+
except Exception: # pylint: disable=broad-except
|
273
|
+
# Not a blocking error.
|
274
|
+
temperatures = None
|
347
275
|
|
348
276
|
Logger.debug("Json temperature data: %s", temperatures)
|
349
277
|
|
350
278
|
# Transform all the data into the target structure.
|
351
|
-
|
279
|
+
if data is None or len(data) == 0:
|
280
|
+
return res
|
281
|
+
|
282
|
+
daily = JsonParser.parse(json.dumps(data), json.dumps(temperatures), pceIdentifier)
|
352
283
|
|
353
284
|
Logger.debug("Processed daily data: %s", daily)
|
354
285
|
|
@@ -368,13 +299,28 @@ class JsonWebDataSource(WebDataSource): # pylint: disable=too-few-public-method
|
|
368
299
|
# ------------------------------------------------------------------------------------------------------------
|
369
300
|
class JsonFileDataSource(IDataSource): # pylint: disable=too-few-public-methods
|
370
301
|
|
302
|
+
# ------------------------------------------------------
|
371
303
|
def __init__(self, consumptionJsonFile: str, temperatureJsonFile):
|
372
304
|
|
373
305
|
self.__consumptionJsonFile = consumptionJsonFile
|
374
306
|
self.__temperatureJsonFile = temperatureJsonFile
|
375
307
|
|
308
|
+
# ------------------------------------------------------
|
309
|
+
def login(self):
|
310
|
+
pass
|
311
|
+
|
312
|
+
# ------------------------------------------------------
|
313
|
+
def logout(self):
|
314
|
+
pass
|
315
|
+
|
316
|
+
# ------------------------------------------------------
|
317
|
+
def get_pce_identifiers(self) -> list[str]:
|
318
|
+
|
319
|
+
return ["0123456789"]
|
320
|
+
|
321
|
+
# ------------------------------------------------------
|
376
322
|
def load(
|
377
|
-
self, pceIdentifier: str, startDate: date, endDate: date, frequencies: Optional[
|
323
|
+
self, pceIdentifier: str, startDate: date, endDate: date, frequencies: Optional[list[Frequency]] = None
|
378
324
|
) -> MeterReadingsByFrequency:
|
379
325
|
|
380
326
|
res = {}
|
@@ -409,12 +355,27 @@ class TestDataSource(IDataSource): # pylint: disable=too-few-public-methods
|
|
409
355
|
|
410
356
|
__test__ = False # Will not be discovered as a test
|
411
357
|
|
358
|
+
# ------------------------------------------------------
|
412
359
|
def __init__(self):
|
413
360
|
|
414
361
|
pass
|
415
362
|
|
363
|
+
# ------------------------------------------------------
|
364
|
+
def login(self):
|
365
|
+
pass
|
366
|
+
|
367
|
+
# ------------------------------------------------------
|
368
|
+
def logout(self):
|
369
|
+
pass
|
370
|
+
|
371
|
+
# ------------------------------------------------------
|
372
|
+
def get_pce_identifiers(self) -> list[str]:
|
373
|
+
|
374
|
+
return ["0123456789"]
|
375
|
+
|
376
|
+
# ------------------------------------------------------
|
416
377
|
def load(
|
417
|
-
self, pceIdentifier: str, startDate: date, endDate: date, frequencies: Optional[
|
378
|
+
self, pceIdentifier: str, startDate: date, endDate: date, frequencies: Optional[list[Frequency]] = None
|
418
379
|
) -> MeterReadingsByFrequency:
|
419
380
|
|
420
381
|
res = dict[str, Any]()
|
@@ -440,7 +401,7 @@ class TestDataSource(IDataSource): # pylint: disable=too-few-public-methods
|
|
440
401
|
)
|
441
402
|
|
442
403
|
with open(dataSampleFilename, mode="r", encoding="utf-8") as jsonFile:
|
443
|
-
res[frequency.value] = cast(
|
404
|
+
res[frequency.value] = cast(list[dict[PropertyName, Any]], json.load(jsonFile))
|
444
405
|
|
445
406
|
return res
|
446
407
|
|
@@ -465,19 +426,19 @@ class FrequencyConverter:
|
|
465
426
|
|
466
427
|
# ------------------------------------------------------
|
467
428
|
@staticmethod
|
468
|
-
def computeHourly(daily:
|
429
|
+
def computeHourly(daily: list[dict[str, Any]]) -> list[dict[str, Any]]: # pylint: disable=unused-argument
|
469
430
|
|
470
431
|
return []
|
471
432
|
|
472
433
|
# ------------------------------------------------------
|
473
434
|
@staticmethod
|
474
|
-
def computeDaily(daily:
|
435
|
+
def computeDaily(daily: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
475
436
|
|
476
437
|
return daily
|
477
438
|
|
478
439
|
# ------------------------------------------------------
|
479
440
|
@staticmethod
|
480
|
-
def computeWeekly(daily:
|
441
|
+
def computeWeekly(daily: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
481
442
|
|
482
443
|
df = pd.DataFrame(daily)
|
483
444
|
|
@@ -533,13 +494,13 @@ class FrequencyConverter:
|
|
533
494
|
# Select target columns.
|
534
495
|
df = df[["time_period", "start_index_m3", "end_index_m3", "volume_m3", "energy_kwh", "timestamp"]]
|
535
496
|
|
536
|
-
res = cast(
|
497
|
+
res = cast(list[dict[str, Any]], df.to_dict("records"))
|
537
498
|
|
538
499
|
return res
|
539
500
|
|
540
501
|
# ------------------------------------------------------
|
541
502
|
@staticmethod
|
542
|
-
def computeMonthly(daily:
|
503
|
+
def computeMonthly(daily: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
543
504
|
|
544
505
|
df = pd.DataFrame(daily)
|
545
506
|
|
@@ -581,13 +542,13 @@ class FrequencyConverter:
|
|
581
542
|
# Select target columns.
|
582
543
|
df = df[["time_period", "start_index_m3", "end_index_m3", "volume_m3", "energy_kwh", "timestamp"]]
|
583
544
|
|
584
|
-
res = cast(
|
545
|
+
res = cast(list[dict[str, Any]], df.to_dict("records"))
|
585
546
|
|
586
547
|
return res
|
587
548
|
|
588
549
|
# ------------------------------------------------------
|
589
550
|
@staticmethod
|
590
|
-
def computeYearly(daily:
|
551
|
+
def computeYearly(daily: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
591
552
|
|
592
553
|
df = pd.DataFrame(daily)
|
593
554
|
|
@@ -624,6 +585,6 @@ class FrequencyConverter:
|
|
624
585
|
# Select target columns.
|
625
586
|
df = df[["time_period", "start_index_m3", "end_index_m3", "volume_m3", "energy_kwh", "timestamp"]]
|
626
587
|
|
627
|
-
res = cast(
|
588
|
+
res = cast(list[dict[str, Any]], df.to_dict("records"))
|
628
589
|
|
629
590
|
return res
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import logging
|
2
2
|
from datetime import datetime
|
3
|
-
from typing import Any
|
3
|
+
from typing import Any
|
4
4
|
|
5
5
|
from openpyxl import load_workbook
|
6
6
|
from openpyxl.cell.cell import Cell
|
@@ -18,7 +18,7 @@ class ExcelParser: # pylint: disable=too-few-public-methods
|
|
18
18
|
|
19
19
|
# ------------------------------------------------------
|
20
20
|
@staticmethod
|
21
|
-
def parse(dataFilename: str, dataReadingFrequency: Frequency) ->
|
21
|
+
def parse(dataFilename: str, dataReadingFrequency: Frequency) -> list[dict[str, Any]]:
|
22
22
|
|
23
23
|
parseByFrequency = {
|
24
24
|
Frequency.HOURLY: ExcelParser.__parseHourly,
|
@@ -43,7 +43,7 @@ class ExcelParser: # pylint: disable=too-few-public-methods
|
|
43
43
|
|
44
44
|
# ------------------------------------------------------
|
45
45
|
@staticmethod
|
46
|
-
def __fillRow(row:
|
46
|
+
def __fillRow(row: dict, propertyName: str, cell: Cell, isNumber: bool):
|
47
47
|
|
48
48
|
if cell.value is not None:
|
49
49
|
if isNumber:
|
@@ -57,12 +57,12 @@ class ExcelParser: # pylint: disable=too-few-public-methods
|
|
57
57
|
|
58
58
|
# ------------------------------------------------------
|
59
59
|
@staticmethod
|
60
|
-
def __parseHourly(worksheet: Worksheet) ->
|
60
|
+
def __parseHourly(worksheet: Worksheet) -> list[dict[str, Any]]: # pylint: disable=unused-argument
|
61
61
|
return []
|
62
62
|
|
63
63
|
# ------------------------------------------------------
|
64
64
|
@staticmethod
|
65
|
-
def __parseDaily(worksheet: Worksheet) ->
|
65
|
+
def __parseDaily(worksheet: Worksheet) -> list[dict[str, Any]]:
|
66
66
|
|
67
67
|
res = []
|
68
68
|
|
@@ -91,7 +91,7 @@ class ExcelParser: # pylint: disable=too-few-public-methods
|
|
91
91
|
|
92
92
|
# ------------------------------------------------------
|
93
93
|
@staticmethod
|
94
|
-
def __parseWeekly(worksheet: Worksheet) ->
|
94
|
+
def __parseWeekly(worksheet: Worksheet) -> list[dict[str, Any]]:
|
95
95
|
|
96
96
|
res = []
|
97
97
|
|
@@ -115,7 +115,7 @@ class ExcelParser: # pylint: disable=too-few-public-methods
|
|
115
115
|
|
116
116
|
# ------------------------------------------------------
|
117
117
|
@staticmethod
|
118
|
-
def __parseMonthly(worksheet: Worksheet) ->
|
118
|
+
def __parseMonthly(worksheet: Worksheet) -> list[dict[str, Any]]:
|
119
119
|
|
120
120
|
res = []
|
121
121
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import json
|
2
2
|
import logging
|
3
3
|
from datetime import datetime
|
4
|
-
from typing import Any
|
4
|
+
from typing import Any
|
5
5
|
|
6
6
|
from pygazpar.enum import PropertyName
|
7
7
|
|
@@ -17,7 +17,7 @@ class JsonParser: # pylint: disable=too-few-public-methods
|
|
17
17
|
|
18
18
|
# ------------------------------------------------------
|
19
19
|
@staticmethod
|
20
|
-
def parse(jsonStr: str, temperaturesStr: str, pceIdentifier: str) ->
|
20
|
+
def parse(jsonStr: str, temperaturesStr: str, pceIdentifier: str) -> list[dict[str, Any]]:
|
21
21
|
|
22
22
|
res = []
|
23
23
|
|
@@ -1,64 +0,0 @@
|
|
1
|
-
import logging
|
2
|
-
from datetime import date, timedelta
|
3
|
-
from typing import List, Optional
|
4
|
-
|
5
|
-
from pygazpar.datasource import IDataSource, MeterReadingsByFrequency
|
6
|
-
from pygazpar.enum import Frequency
|
7
|
-
|
8
|
-
AUTH_NONCE_URL = "https://monespace.grdf.fr/client/particulier/accueil"
|
9
|
-
LOGIN_URL = "https://login.monespace.grdf.fr/sofit-account-api/api/v1/auth"
|
10
|
-
LOGIN_HEADER = {"domain": "grdf.fr"}
|
11
|
-
LOGIN_PAYLOAD = """{{
|
12
|
-
"email": "{0}",
|
13
|
-
"password": "{1}",
|
14
|
-
"capp": "meg",
|
15
|
-
"goto": "https://sofa-connexion.grdf.fr:443/openam/oauth2/externeGrdf/authorize?response_type=code&scope=openid%20profile%20email%20infotravaux%20%2Fv1%2Faccreditation%20%2Fv1%2Faccreditations%20%2Fdigiconso%2Fv1%20%2Fdigiconso%2Fv1%2Fconsommations%20new_meg&client_id=prod_espaceclient&state=0&redirect_uri=https%3A%2F%2Fmonespace.grdf.fr%2F_codexch&nonce={2}&by_pass_okta=1&capp=meg"}}"""
|
16
|
-
DATA_URL = "https://monespace.grdf.fr/api/e-conso/pce/consommation/informatives/telecharger?dateDebut={1}&dateFin={2}&frequence={0}&pceList%5B%5D={3}"
|
17
|
-
DATA_FILENAME = "Donnees_informatives_*.xlsx"
|
18
|
-
|
19
|
-
DEFAULT_TMP_DIRECTORY = "/tmp"
|
20
|
-
DEFAULT_LAST_N_DAYS = 365
|
21
|
-
|
22
|
-
|
23
|
-
Logger = logging.getLogger(__name__)
|
24
|
-
|
25
|
-
|
26
|
-
# ------------------------------------------------------------------------------------------------------------
|
27
|
-
class Client:
|
28
|
-
|
29
|
-
# ------------------------------------------------------
|
30
|
-
def __init__(self, dataSource: IDataSource):
|
31
|
-
self.__dataSource = dataSource
|
32
|
-
|
33
|
-
# ------------------------------------------------------
|
34
|
-
def loadSince(
|
35
|
-
self, pceIdentifier: str, lastNDays: int = DEFAULT_LAST_N_DAYS, frequencies: Optional[List[Frequency]] = None
|
36
|
-
) -> MeterReadingsByFrequency:
|
37
|
-
|
38
|
-
try:
|
39
|
-
endDate = date.today()
|
40
|
-
startDate = endDate + timedelta(days=-lastNDays)
|
41
|
-
|
42
|
-
res = self.loadDateRange(pceIdentifier, startDate, endDate, frequencies)
|
43
|
-
except Exception:
|
44
|
-
Logger.error("An unexpected error occured while loading the data", exc_info=True)
|
45
|
-
raise
|
46
|
-
|
47
|
-
return res
|
48
|
-
|
49
|
-
# ------------------------------------------------------
|
50
|
-
def loadDateRange(
|
51
|
-
self, pceIdentifier: str, startDate: date, endDate: date, frequencies: Optional[List[Frequency]] = None
|
52
|
-
) -> MeterReadingsByFrequency:
|
53
|
-
|
54
|
-
Logger.debug("Start loading the data...")
|
55
|
-
|
56
|
-
try:
|
57
|
-
res = self.__dataSource.load(pceIdentifier, startDate, endDate, frequencies)
|
58
|
-
|
59
|
-
Logger.debug("The data load terminates normally")
|
60
|
-
except Exception:
|
61
|
-
Logger.error("An unexpected error occured while loading the data", exc_info=True)
|
62
|
-
raise
|
63
|
-
|
64
|
-
return res
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|