pygazpar 1.3.0a2__py310-none-any.whl → 1.3.0a6__py310-none-any.whl
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.
- pygazpar/datasource.py +115 -46
- pygazpar/version.py +1 -1
- {pygazpar-1.3.0a2.dist-info → pygazpar-1.3.0a6.dist-info}/METADATA +7 -4
- {pygazpar-1.3.0a2.dist-info → pygazpar-1.3.0a6.dist-info}/RECORD +9 -9
- {pygazpar-1.3.0a2.dist-info → pygazpar-1.3.0a6.dist-info}/WHEEL +1 -1
- tests/test_client.py +1 -1
- {pygazpar-1.3.0a2.dist-info → pygazpar-1.3.0a6.dist-info}/LICENSE.md +0 -0
- {pygazpar-1.3.0a2.dist-info → pygazpar-1.3.0a6.dist-info}/entry_points.txt +0 -0
- {pygazpar-1.3.0a2.dist-info → pygazpar-1.3.0a6.dist-info}/top_level.txt +0 -0
pygazpar/datasource.py
CHANGED
@@ -2,7 +2,9 @@ import logging
|
|
2
2
|
import glob
|
3
3
|
import os
|
4
4
|
import json
|
5
|
+
import time
|
5
6
|
import pandas as pd
|
7
|
+
import http.cookiejar
|
6
8
|
from abc import ABC, abstractmethod
|
7
9
|
from typing import Any, List, Dict, cast, Optional
|
8
10
|
from requests import Session
|
@@ -11,15 +13,22 @@ from pygazpar.enum import Frequency, PropertyName
|
|
11
13
|
from pygazpar.excelparser import ExcelParser
|
12
14
|
from pygazpar.jsonparser import JsonParser
|
13
15
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
LOGIN_PAYLOAD = """{{
|
18
|
-
"email": "{0}",
|
16
|
+
SESSION_TOKEN_URL = "https://connexion.grdf.fr/api/v1/authn"
|
17
|
+
SESSION_TOKEN_PAYLOAD = """{{
|
18
|
+
"username": "{0}",
|
19
19
|
"password": "{1}",
|
20
|
-
"
|
21
|
-
|
22
|
-
|
20
|
+
"options": {{
|
21
|
+
"multiOptionalFactorEnroll": "false",
|
22
|
+
"warnBeforePasswordExpired": "false"
|
23
|
+
}}
|
24
|
+
}}"""
|
25
|
+
|
26
|
+
AUTH_TOKEN_URL = "https://connexion.grdf.fr/login/sessionCookieRedirect"
|
27
|
+
AUTH_TOKEN_PARAMS = """{{
|
28
|
+
"checkAccountSetupComplete": "true",
|
29
|
+
"token": "{0}",
|
30
|
+
"redirectUrl": "https://monespace.grdf.fr"
|
31
|
+
}}"""
|
23
32
|
|
24
33
|
Logger = logging.getLogger(__name__)
|
25
34
|
|
@@ -50,56 +59,59 @@ class WebDataSource(IDataSource):
|
|
50
59
|
# ------------------------------------------------------
|
51
60
|
def load(self, pceIdentifier: str, startDate: date, endDate: date, frequencies: Optional[List[Frequency]] = None) -> MeterReadingsByFrequency:
|
52
61
|
|
53
|
-
|
54
|
-
|
55
|
-
session.headers.update(LOGIN_HEADER)
|
56
|
-
|
57
|
-
self._login(session, self.__username, self.__password)
|
62
|
+
auth_token = self._login(self.__username, self.__password)
|
58
63
|
|
59
|
-
res = self._loadFromSession(
|
64
|
+
res = self._loadFromSession(auth_token, pceIdentifier, startDate, endDate, frequencies)
|
60
65
|
|
61
66
|
Logger.debug("The data update terminates normally")
|
62
67
|
|
63
68
|
return res
|
64
69
|
|
65
70
|
# ------------------------------------------------------
|
66
|
-
def _login(self,
|
71
|
+
def _login(self, username: str, password: str) -> str:
|
67
72
|
|
68
|
-
|
69
|
-
session.
|
70
|
-
|
71
|
-
|
72
|
-
auth_nonce = session.cookies.get("auth_nonce")
|
73
|
+
session = Session()
|
74
|
+
session.headers.update({"domain": "grdf.fr"})
|
75
|
+
session.headers.update({"Content-Type": "application/json"})
|
76
|
+
session.headers.update({"X-Requested-With": "XMLHttpRequest"})
|
73
77
|
|
74
|
-
|
75
|
-
payload = LOGIN_PAYLOAD.format(username, password, auth_nonce)
|
78
|
+
payload = SESSION_TOKEN_PAYLOAD.format(username, password)
|
76
79
|
|
77
|
-
|
78
|
-
data = json.loads(payload)
|
80
|
+
response = session.post(SESSION_TOKEN_URL, data=payload)
|
79
81
|
|
80
|
-
|
81
|
-
|
82
|
+
if response.status_code != 200:
|
83
|
+
raise Exception(f"An error occurred while logging in. Status code: {response.status_code} - {response.text}")
|
82
84
|
|
83
|
-
|
84
|
-
loginData = response.json()
|
85
|
+
session_token = response.json().get("sessionToken")
|
85
86
|
|
86
|
-
|
87
|
+
Logger.debug("Session token: %s", session_token)
|
88
|
+
|
89
|
+
jar = http.cookiejar.CookieJar()
|
87
90
|
|
88
|
-
|
89
|
-
|
91
|
+
session = Session()
|
92
|
+
session.headers.update({"Content-Type": "application/json"})
|
93
|
+
session.headers.update({"X-Requested-With": "XMLHttpRequest"})
|
94
|
+
|
95
|
+
params = json.loads(AUTH_TOKEN_PARAMS.format(session_token))
|
96
|
+
|
97
|
+
response = session.get(AUTH_TOKEN_URL, params=params, allow_redirects=True, cookies=jar)
|
98
|
+
|
99
|
+
if response.status_code != 200:
|
100
|
+
raise Exception(f"An error occurred while getting the auth token. Status code: {response.status_code} - {response.text}")
|
90
101
|
|
91
|
-
|
92
|
-
|
102
|
+
auth_token = session.cookies.get("auth_token", domain="monespace.grdf.fr")
|
103
|
+
|
104
|
+
return auth_token
|
93
105
|
|
94
106
|
@abstractmethod
|
95
|
-
def _loadFromSession(self,
|
107
|
+
def _loadFromSession(self, auth_token: str, pceIdentifier: str, startDate: date, endDate: date, frequencies: Optional[List[Frequency]] = None) -> MeterReadingsByFrequency:
|
96
108
|
pass
|
97
109
|
|
98
110
|
|
99
111
|
# ------------------------------------------------------------------------------------------------------------
|
100
112
|
class ExcelWebDataSource(WebDataSource):
|
101
113
|
|
102
|
-
DATA_URL = "https://monespace.grdf.fr/api/e-conso/pce/consommation/informatives/telecharger?dateDebut={0}&dateFin={1}&frequence={3}&pceList
|
114
|
+
DATA_URL = "https://monespace.grdf.fr/api/e-conso/pce/consommation/informatives/telecharger?dateDebut={0}&dateFin={1}&frequence={3}&pceList[]={2}"
|
103
115
|
|
104
116
|
DATE_FORMAT = "%Y-%m-%d"
|
105
117
|
|
@@ -121,7 +133,7 @@ class ExcelWebDataSource(WebDataSource):
|
|
121
133
|
self.__tmpDirectory = tmpDirectory
|
122
134
|
|
123
135
|
# ------------------------------------------------------
|
124
|
-
def _loadFromSession(self,
|
136
|
+
def _loadFromSession(self, auth_token: str, pceIdentifier: str, startDate: date, endDate: date, frequencies: Optional[List[Frequency]] = None) -> MeterReadingsByFrequency:
|
125
137
|
|
126
138
|
res = {}
|
127
139
|
|
@@ -145,11 +157,31 @@ class ExcelWebDataSource(WebDataSource):
|
|
145
157
|
# Inject parameters.
|
146
158
|
downloadUrl = ExcelWebDataSource.DATA_URL.format(startDate.strftime(ExcelWebDataSource.DATE_FORMAT), endDate.strftime(ExcelWebDataSource.DATE_FORMAT), pceIdentifier, ExcelWebDataSource.FREQUENCY_VALUES[frequency])
|
147
159
|
|
148
|
-
session.get(downloadUrl) # First request does not return anything : strange...
|
149
|
-
|
150
160
|
Logger.debug(f"Loading data of frequency {ExcelWebDataSource.FREQUENCY_VALUES[frequency]} from {startDate.strftime(ExcelWebDataSource.DATE_FORMAT)} to {endDate.strftime(ExcelWebDataSource.DATE_FORMAT)}")
|
151
161
|
|
152
|
-
|
162
|
+
# Retry mechanism.
|
163
|
+
retry = 10
|
164
|
+
while retry > 0:
|
165
|
+
|
166
|
+
# Create a session.
|
167
|
+
session = Session()
|
168
|
+
session.headers.update({"Host": "monespace.grdf.fr"})
|
169
|
+
session.headers.update({"Domain": "grdf.fr"})
|
170
|
+
session.headers.update({"X-Requested-With": "XMLHttpRequest"})
|
171
|
+
session.headers.update({"Accept": "application/json"})
|
172
|
+
session.cookies.set("auth_token", auth_token, domain="monespace.grdf.fr")
|
173
|
+
|
174
|
+
try:
|
175
|
+
self.__downloadFile(session, downloadUrl, self.__tmpDirectory)
|
176
|
+
break
|
177
|
+
except Exception as e:
|
178
|
+
|
179
|
+
if retry == 1:
|
180
|
+
raise e
|
181
|
+
|
182
|
+
Logger.error("An error occurred while loading data. Retry in 3 seconds.")
|
183
|
+
time.sleep(3)
|
184
|
+
retry -= 1
|
153
185
|
|
154
186
|
# Load the XLSX file into the data structure
|
155
187
|
file_list = glob.glob(data_file_path_pattern)
|
@@ -159,7 +191,11 @@ class ExcelWebDataSource(WebDataSource):
|
|
159
191
|
|
160
192
|
for filename in file_list:
|
161
193
|
res[frequency.value] = ExcelParser.parse(filename, frequency if frequency != Frequency.YEARLY else Frequency.DAILY)
|
162
|
-
|
194
|
+
try:
|
195
|
+
# openpyxl does not close the file properly.
|
196
|
+
os.remove(filename)
|
197
|
+
except Exception:
|
198
|
+
pass
|
163
199
|
|
164
200
|
# We compute yearly from daily data.
|
165
201
|
if frequency == Frequency.YEARLY:
|
@@ -172,6 +208,12 @@ class ExcelWebDataSource(WebDataSource):
|
|
172
208
|
|
173
209
|
response = session.get(url)
|
174
210
|
|
211
|
+
if "text/html" in response.headers.get("Content-Type"):
|
212
|
+
raise Exception("An error occurred while loading data. Please check your credentials.")
|
213
|
+
|
214
|
+
if response.status_code != 200:
|
215
|
+
raise Exception(f"An error occurred while loading data. Status code: {response.status_code} - {response.text}")
|
216
|
+
|
175
217
|
response.raise_for_status()
|
176
218
|
|
177
219
|
filename = response.headers["Content-Disposition"].split("filename=")[1]
|
@@ -210,7 +252,7 @@ class ExcelFileDataSource(IDataSource):
|
|
210
252
|
# ------------------------------------------------------------------------------------------------------------
|
211
253
|
class JsonWebDataSource(WebDataSource):
|
212
254
|
|
213
|
-
DATA_URL = "https://monespace.grdf.fr/api/e-conso/pce/consommation/informatives?dateDebut={0}&dateFin={1}&pceList
|
255
|
+
DATA_URL = "https://monespace.grdf.fr/api/e-conso/pce/consommation/informatives?dateDebut={0}&dateFin={1}&pceList[]={2}"
|
214
256
|
|
215
257
|
TEMPERATURES_URL = "https://monespace.grdf.fr/api/e-conso/pce/{0}/meteo?dateFinPeriode={1}&nbJours={2}"
|
216
258
|
|
@@ -222,7 +264,7 @@ class JsonWebDataSource(WebDataSource):
|
|
222
264
|
|
223
265
|
super().__init__(username, password)
|
224
266
|
|
225
|
-
def _loadFromSession(self,
|
267
|
+
def _loadFromSession(self, auth_token: str, pceIdentifier: str, startDate: date, endDate: date, frequencies: Optional[List[Frequency]] = None) -> MeterReadingsByFrequency:
|
226
268
|
|
227
269
|
res = {}
|
228
270
|
|
@@ -237,11 +279,38 @@ class JsonWebDataSource(WebDataSource):
|
|
237
279
|
# Data URL: Inject parameters.
|
238
280
|
downloadUrl = JsonWebDataSource.DATA_URL.format(startDate.strftime(JsonWebDataSource.INPUT_DATE_FORMAT), endDate.strftime(JsonWebDataSource.INPUT_DATE_FORMAT), pceIdentifier)
|
239
281
|
|
240
|
-
#
|
241
|
-
|
282
|
+
# Retry mechanism.
|
283
|
+
retry = 10
|
284
|
+
while retry > 0:
|
285
|
+
|
286
|
+
# Create a session.
|
287
|
+
session = Session()
|
288
|
+
session.headers.update({"Host": "monespace.grdf.fr"})
|
289
|
+
session.headers.update({"Domain": "grdf.fr"})
|
290
|
+
session.headers.update({"X-Requested-With": "XMLHttpRequest"})
|
291
|
+
session.headers.update({"Accept": "application/json"})
|
292
|
+
session.cookies.set("auth_token", auth_token, domain="monespace.grdf.fr")
|
293
|
+
|
294
|
+
try:
|
295
|
+
response = session.get(downloadUrl)
|
296
|
+
|
297
|
+
if "text/html" in response.headers.get("Content-Type"):
|
298
|
+
raise Exception("An error occurred while loading data. Please check your credentials.")
|
299
|
+
|
300
|
+
if response.status_code != 200:
|
301
|
+
raise Exception(f"An error occurred while loading data. Status code: {response.status_code} - {response.text}")
|
302
|
+
|
303
|
+
break
|
304
|
+
except Exception as e:
|
305
|
+
|
306
|
+
if retry == 1:
|
307
|
+
raise e
|
308
|
+
|
309
|
+
Logger.error("An error occurred while loading data. Retry in 3 seconds.")
|
310
|
+
time.sleep(3)
|
311
|
+
retry -= 1
|
242
312
|
|
243
|
-
|
244
|
-
data = session.get(downloadUrl).text
|
313
|
+
data = response.text
|
245
314
|
|
246
315
|
# Temperatures URL: Inject parameters.
|
247
316
|
endDate = date.today() - timedelta(days=1) if endDate >= date.today() else endDate
|
pygazpar/version.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "1.3.
|
1
|
+
__version__ = "1.3.0a6"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: pygazpar
|
3
|
-
Version: 1.3.
|
3
|
+
Version: 1.3.0a6
|
4
4
|
Summary: Retrieve gas consumption from GrDF web site (French Gas Company)
|
5
5
|
Home-page: https://github.com/ssenart/pygazpar
|
6
6
|
Download-URL: https://github.com/ssenart/pygazpar/releases
|
@@ -25,8 +25,8 @@ Classifier: Programming Language :: Python :: 3.11
|
|
25
25
|
Requires-Python: >=3.7
|
26
26
|
Description-Content-Type: text/markdown
|
27
27
|
License-File: LICENSE.md
|
28
|
-
Requires-Dist: openpyxl
|
29
|
-
Requires-Dist: requests
|
28
|
+
Requires-Dist: openpyxl >=2.6.3
|
29
|
+
Requires-Dist: requests >=2.26.0
|
30
30
|
Requires-Dist: pandas
|
31
31
|
|
32
32
|
# PyGazpar
|
@@ -214,9 +214,12 @@ All notable changes to this project will be documented in this file.
|
|
214
214
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
215
215
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
216
216
|
|
217
|
-
|
217
|
+
|
218
|
+
## [1.2.1](https://github.com/ssenart/PyGazpar/compare/1.2.0...1.2.1) - 2024-05-04
|
218
219
|
|
219
220
|
### Fixed
|
221
|
+
- [#64](https://github.com/ssenart/PyGazpar/issues/64): [Issue] Captcha failed issue.
|
222
|
+
|
220
223
|
- [#63](https://github.com/ssenart/PyGazpar/issues/63): [Bug] If the latest received consumption is Sunday, then the last weekly period is duplicated.
|
221
224
|
|
222
225
|
## [1.2.0](https://github.com/ssenart/PyGazpar/compare/1.1.6...1.2.0) - 2022-12-16
|
@@ -1,11 +1,11 @@
|
|
1
1
|
pygazpar/__init__.py,sha256=qshO_XZbDA2Wrt80ABDs0MoScqJytClAuIJjAnILglk,309
|
2
2
|
pygazpar/__main__.py,sha256=Pt3PInX7QiWcs0aBKZN90NTaU8KFnrQiZ5Hsow1eR5U,3177
|
3
3
|
pygazpar/client.py,sha256=JdVm0jZbeibwtTumcRbUSFadfXnCUClPMjL95_J6p5Y,2595
|
4
|
-
pygazpar/datasource.py,sha256=
|
4
|
+
pygazpar/datasource.py,sha256=gqOREo9OQaBmqWgaDIgShJrP1dZNVFJoIxUu9Rfxmec,21374
|
5
5
|
pygazpar/enum.py,sha256=3ZCk4SziXF6pxgP3MuQ1qxYfqB3X5DOV8Rtd0GHsK9w,898
|
6
6
|
pygazpar/excelparser.py,sha256=glWlbj22pxYjHGKurOFmhzcVAoWCvfOHn7_Y6GgHUPo,5915
|
7
7
|
pygazpar/jsonparser.py,sha256=AWdU3h7UohsOov8HpeP8GNuqcnDmM4r3I7-CI_crDvA,1804
|
8
|
-
pygazpar/version.py,sha256=
|
8
|
+
pygazpar/version.py,sha256=rcyBYFWxUArZ5y56YkLypabOiwSZfCl48nXHK0ebB20,24
|
9
9
|
pygazpar/resources/daily_data_sample.json,sha256=YJovtrNUMs257magTfyxiewLmecySFypcelbGFUUeT8,199583
|
10
10
|
pygazpar/resources/hourly_data_sample.json,sha256=N1F-Xz3GaBn2H1p7uKzhkhKCQV8QVR0t76XD6wmFtXA,3
|
11
11
|
pygazpar/resources/monthly_data_sample.json,sha256=yrr4SqrB2MubeVU2HX_FRDZKHIhC0LXCqkO1iqnFWcg,3351
|
@@ -16,12 +16,12 @@ samples/excelSample.py,sha256=ltAl-bBz9-U9YI802JpcIswra-vDS7tR_KL5VNdxJ5c,765
|
|
16
16
|
samples/jsonSample.py,sha256=sYAIusdEJhZdwDAMgHqoWcwDR0FA2eWhSt_2gL_mJRk,736
|
17
17
|
samples/testSample.py,sha256=UeirdEtezHwfZDv_75oxul17YzGWn5yZuHfJYTF3Ez0,387
|
18
18
|
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
19
|
-
tests/test_client.py,sha256=
|
19
|
+
tests/test_client.py,sha256=OwYBeNC66WiykU1IUEf4eZacAU49xQJXsFQY6kYiXCQ,5111
|
20
20
|
tests/test_datafileparser.py,sha256=nAeUpOHtelblMpmbrrnf-2GuMjK5ai65veDoymceprE,818
|
21
21
|
tests/test_datasource.py,sha256=2BCrnUh9ZbR_dAdvKT9498u5a-jGnC4aZafP7V9iZn8,5983
|
22
|
-
pygazpar-1.3.
|
23
|
-
pygazpar-1.3.
|
24
|
-
pygazpar-1.3.
|
25
|
-
pygazpar-1.3.
|
26
|
-
pygazpar-1.3.
|
27
|
-
pygazpar-1.3.
|
22
|
+
pygazpar-1.3.0a6.dist-info/LICENSE.md,sha256=XsCJx_7_BC9tvmE0ZxS1cTNR7ekurog_ea9ybdZ-8tc,1073
|
23
|
+
pygazpar-1.3.0a6.dist-info/METADATA,sha256=RMMJQhnVA9M0vDyqxi3tXz50SeahGId-MXGAzLtbMfE,17764
|
24
|
+
pygazpar-1.3.0a6.dist-info/WHEEL,sha256=zivVvCKG3z2j-gmh6yOhs4fqAlXtQ7W3cw4l2J-wg1c,94
|
25
|
+
pygazpar-1.3.0a6.dist-info/entry_points.txt,sha256=dNJjC6RYY3FeJIe0hZL9Kcr6vKEx1qkZ71e6Slocb7I,52
|
26
|
+
pygazpar-1.3.0a6.dist-info/top_level.txt,sha256=P7qn-XtanDPBLQsTvjvLV71wH8RK0DYbx8tzN_rDS70,23
|
27
|
+
pygazpar-1.3.0a6.dist-info/RECORD,,
|
tests/test_client.py
CHANGED
@@ -75,7 +75,7 @@ class TestClient:
|
|
75
75
|
assert (len(data[Frequency.MONTHLY.value]) >= 12 and len(data[Frequency.MONTHLY.value]) <= 13)
|
76
76
|
|
77
77
|
def test_yearly_jsonweb(self):
|
78
|
-
client = Client(
|
78
|
+
client = Client(JsonWebDataSource(self.__username, self.__password))
|
79
79
|
|
80
80
|
data = client.loadSince(self.__pceIdentifier, 365, [Frequency.YEARLY])
|
81
81
|
|
File without changes
|
File without changes
|
File without changes
|