pygazpar 1.3.0a2__tar.gz → 1.3.0a6__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. {pygazpar-1.3.0a2/pygazpar.egg-info → pygazpar-1.3.0a6}/PKG-INFO +5 -2
  2. {pygazpar-1.3.0a2 → pygazpar-1.3.0a6}/pygazpar/datasource.py +115 -46
  3. pygazpar-1.3.0a6/pygazpar/version.py +1 -0
  4. {pygazpar-1.3.0a2 → pygazpar-1.3.0a6/pygazpar.egg-info}/PKG-INFO +5 -2
  5. {pygazpar-1.3.0a2 → pygazpar-1.3.0a6}/tests/test_client.py +1 -1
  6. pygazpar-1.3.0a2/pygazpar/version.py +0 -1
  7. {pygazpar-1.3.0a2 → pygazpar-1.3.0a6}/LICENSE.md +0 -0
  8. {pygazpar-1.3.0a2 → pygazpar-1.3.0a6}/MANIFEST.in +0 -0
  9. {pygazpar-1.3.0a2 → pygazpar-1.3.0a6}/README.md +0 -0
  10. {pygazpar-1.3.0a2 → pygazpar-1.3.0a6}/pygazpar/__init__.py +0 -0
  11. {pygazpar-1.3.0a2 → pygazpar-1.3.0a6}/pygazpar/__main__.py +0 -0
  12. {pygazpar-1.3.0a2 → pygazpar-1.3.0a6}/pygazpar/client.py +0 -0
  13. {pygazpar-1.3.0a2 → pygazpar-1.3.0a6}/pygazpar/enum.py +0 -0
  14. {pygazpar-1.3.0a2 → pygazpar-1.3.0a6}/pygazpar/excelparser.py +0 -0
  15. {pygazpar-1.3.0a2 → pygazpar-1.3.0a6}/pygazpar/jsonparser.py +0 -0
  16. {pygazpar-1.3.0a2 → pygazpar-1.3.0a6}/pygazpar/resources/daily_data_sample.json +0 -0
  17. {pygazpar-1.3.0a2 → pygazpar-1.3.0a6}/pygazpar/resources/hourly_data_sample.json +0 -0
  18. {pygazpar-1.3.0a2 → pygazpar-1.3.0a6}/pygazpar/resources/monthly_data_sample.json +0 -0
  19. {pygazpar-1.3.0a2 → pygazpar-1.3.0a6}/pygazpar/resources/weekly_data_sample.json +0 -0
  20. {pygazpar-1.3.0a2 → pygazpar-1.3.0a6}/pygazpar/resources/yearly_data_sample.json +0 -0
  21. {pygazpar-1.3.0a2 → pygazpar-1.3.0a6}/pygazpar.egg-info/SOURCES.txt +0 -0
  22. {pygazpar-1.3.0a2 → pygazpar-1.3.0a6}/pygazpar.egg-info/dependency_links.txt +0 -0
  23. {pygazpar-1.3.0a2 → pygazpar-1.3.0a6}/pygazpar.egg-info/entry_points.txt +0 -0
  24. {pygazpar-1.3.0a2 → pygazpar-1.3.0a6}/pygazpar.egg-info/not-zip-safe +0 -0
  25. {pygazpar-1.3.0a2 → pygazpar-1.3.0a6}/pygazpar.egg-info/requires.txt +0 -0
  26. {pygazpar-1.3.0a2 → pygazpar-1.3.0a6}/pygazpar.egg-info/top_level.txt +0 -0
  27. {pygazpar-1.3.0a2 → pygazpar-1.3.0a6}/samples/__init__.py +0 -0
  28. {pygazpar-1.3.0a2 → pygazpar-1.3.0a6}/samples/excelSample.py +0 -0
  29. {pygazpar-1.3.0a2 → pygazpar-1.3.0a6}/samples/jsonSample.py +0 -0
  30. {pygazpar-1.3.0a2 → pygazpar-1.3.0a6}/samples/testSample.py +0 -0
  31. {pygazpar-1.3.0a2 → pygazpar-1.3.0a6}/setup.cfg +0 -0
  32. {pygazpar-1.3.0a2 → pygazpar-1.3.0a6}/setup.py +0 -0
  33. {pygazpar-1.3.0a2 → pygazpar-1.3.0a6}/tests/__init__.py +0 -0
  34. {pygazpar-1.3.0a2 → pygazpar-1.3.0a6}/tests/test_datafileparser.py +0 -0
  35. {pygazpar-1.3.0a2 → pygazpar-1.3.0a6}/tests/test_datasource.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pygazpar
3
- Version: 1.3.0a2
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
  Author: Stephane Senart
@@ -197,9 +197,12 @@ Description: # PyGazpar
197
197
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
198
198
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
199
199
 
200
- ## [1.2.1](https://github.com/ssenart/PyGazpar/compare/1.2.0...1.2.1) - 2022-12-28
200
+
201
+ ## [1.2.1](https://github.com/ssenart/PyGazpar/compare/1.2.0...1.2.1) - 2024-05-04
201
202
 
202
203
  ### Fixed
204
+ - [#64](https://github.com/ssenart/PyGazpar/issues/64): [Issue] Captcha failed issue.
205
+
203
206
  - [#63](https://github.com/ssenart/PyGazpar/issues/63): [Bug] If the latest received consumption is Sunday, then the last weekly period is duplicated.
204
207
 
205
208
  ## [1.2.0](https://github.com/ssenart/PyGazpar/compare/1.1.6...1.2.0) - 2022-12-16
@@ -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
- AUTH_NONCE_URL = "https://monespace.grdf.fr/client/particulier/accueil"
15
- LOGIN_URL = "https://login.monespace.grdf.fr/sofit-account-api/api/v1/auth"
16
- LOGIN_HEADER = {"domain": "grdf.fr"}
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
- "capp": "meg",
21
- "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"}}"""
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
- session = Session()
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(session, pceIdentifier, startDate, endDate, frequencies)
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, session: Session, username: str, password: str):
71
+ def _login(self, username: str, password: str) -> str:
67
72
 
68
- # Get auth_nonce token.
69
- session.get(AUTH_NONCE_URL)
70
- if "auth_nonce" not in session.cookies:
71
- raise Exception("Login error: Cannot get auth_nonce token")
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
- # Build the login payload as a json string.
75
- payload = LOGIN_PAYLOAD.format(username, password, auth_nonce)
78
+ payload = SESSION_TOKEN_PAYLOAD.format(username, password)
76
79
 
77
- # Build the login payload as a python object.
78
- data = json.loads(payload)
80
+ response = session.post(SESSION_TOKEN_URL, data=payload)
79
81
 
80
- # Send the login command.
81
- response = session.post(LOGIN_URL, data=data)
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
- # Check login result.
84
- loginData = response.json()
85
+ session_token = response.json().get("sessionToken")
85
86
 
86
- response.raise_for_status()
87
+ Logger.debug("Session token: %s", session_token)
88
+
89
+ jar = http.cookiejar.CookieJar()
87
90
 
88
- if "status" in loginData and "error" in loginData and loginData["status"] >= 400:
89
- raise Exception(f"{loginData['error']} ({loginData['status']})")
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
- if "state" in loginData and loginData["state"] != "SUCCESS":
92
- raise Exception(loginData["error"])
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, session: Session, pceIdentifier: str, startDate: date, endDate: date, frequencies: Optional[List[Frequency]] = None) -> MeterReadingsByFrequency:
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%5B%5D={2}"
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, session: Session, pceIdentifier: str, startDate: date, endDate: date, frequencies: Optional[List[Frequency]] = None) -> MeterReadingsByFrequency:
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
- self.__downloadFile(session, downloadUrl, self.__tmpDirectory)
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
- os.remove(filename)
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%5B%5D={2}"
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, session: Session, pceIdentifier: str, startDate: date, endDate: date, frequencies: Optional[List[Frequency]] = None) -> MeterReadingsByFrequency:
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
- # First request never returns data.
241
- session.get(downloadUrl)
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
- # Get consumption data.
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
@@ -0,0 +1 @@
1
+ __version__ = "1.3.0a6"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pygazpar
3
- Version: 1.3.0a2
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
  Author: Stephane Senart
@@ -197,9 +197,12 @@ Description: # PyGazpar
197
197
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
198
198
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
199
199
 
200
- ## [1.2.1](https://github.com/ssenart/PyGazpar/compare/1.2.0...1.2.1) - 2022-12-28
200
+
201
+ ## [1.2.1](https://github.com/ssenart/PyGazpar/compare/1.2.0...1.2.1) - 2024-05-04
201
202
 
202
203
  ### Fixed
204
+ - [#64](https://github.com/ssenart/PyGazpar/issues/64): [Issue] Captcha failed issue.
205
+
203
206
  - [#63](https://github.com/ssenart/PyGazpar/issues/63): [Bug] If the latest received consumption is Sunday, then the last weekly period is duplicated.
204
207
 
205
208
  ## [1.2.0](https://github.com/ssenart/PyGazpar/compare/1.1.6...1.2.0) - 2022-12-16
@@ -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(ExcelWebDataSource(self.__username, self.__password, self.__tmp_directory))
78
+ client = Client(JsonWebDataSource(self.__username, self.__password))
79
79
 
80
80
  data = client.loadSince(self.__pceIdentifier, 365, [Frequency.YEARLY])
81
81
 
@@ -1 +0,0 @@
1
- __version__ = "1.3.0a2"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes