Skip to content

Commit d39ee53

Browse files
author
Stéphane Senart
committed
[#84] Add a function to retrieve list of PCE ids and labels from account
1 parent ec372c1 commit d39ee53

File tree

3 files changed

+70
-109
lines changed

3 files changed

+70
-109
lines changed

pygazpar/api_client.py

+32-17
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1-
from requests import Session, Response
2-
from typing import Any
31
import http.cookiejar
42
import json
3+
import logging
4+
import time
5+
import traceback
56
from datetime import date
67
from enum import Enum
7-
import time
8+
from typing import Any
9+
10+
from requests import Response, Session
811

912
SESSION_TOKEN_URL = "https://connexion.grdf.fr/api/v1/authn"
1013
SESSION_TOKEN_PAYLOAD = """{{
@@ -27,6 +30,8 @@
2730

2831
DATE_FORMAT = "%Y-%m-%d"
2932

33+
Logger = logging.getLogger(__name__)
34+
3035

3136
# ------------------------------------------------------
3237
class ConsumptionType(str, Enum):
@@ -36,11 +41,11 @@ class ConsumptionType(str, Enum):
3641

3742
# ------------------------------------------------------
3843
class Frequency(str, Enum):
39-
HOURLY = 'Horaire'
40-
DAILY = 'Journalier'
41-
WEEKLY = 'Hebdomadaire'
42-
MONTHLY = 'Mensuel'
43-
YEARLY = 'Annuel'
44+
HOURLY = "Horaire"
45+
DAILY = "Journalier"
46+
WEEKLY = "Hebdomadaire"
47+
MONTHLY = "Mensuel"
48+
YEARLY = "Annuel"
4449

4550

4651
# ------------------------------------------------------
@@ -114,7 +119,9 @@ def get(self, endpoint: str, params: dict[str, Any]) -> Response:
114119
response = self._session.get(f"{API_BASE_URL}{endpoint}", params=params)
115120

116121
if "text/html" in response.headers.get("Content-Type"): # type: ignore
117-
raise ValueError(f"An error occurred while loading data. Please check your query parameters: {params}")
122+
raise ValueError(
123+
f"An error occurred while loading data. Please check your query parameters: {params}"
124+
)
118125

119126
if response.status_code != 200:
120127
raise ValueError(
@@ -124,7 +131,9 @@ def get(self, endpoint: str, params: dict[str, Any]) -> Response:
124131
break
125132
except Exception as e: # pylint: disable=broad-exception-caught
126133
if retry == 1:
134+
Logger.error(f"An error occurred while loading data. Retry limit reached: {traceback.format_exc()}")
127135
raise e
136+
Logger.warning("An error occurred while loading data. Retry in 3 seconds...")
128137
time.sleep(3)
129138
retry -= 1
130139

@@ -141,14 +150,16 @@ def get_pce_list(self, details: bool = False) -> list[Any]:
141150
return res
142151

143152
# ------------------------------------------------------
144-
def get_pce_consumption(self, consumption_type: ConsumptionType, start_date: date, end_date: date, pce_list: list[str]) -> dict[str, Any]:
153+
def get_pce_consumption(
154+
self, consumption_type: ConsumptionType, start_date: date, end_date: date, pce_list: list[str]
155+
) -> dict[str, Any]:
145156

146157
start = start_date.strftime(DATE_FORMAT)
147158
end = end_date.strftime(DATE_FORMAT)
148159

149160
res = self.get(
150161
f"/e-conso/pce/consommation/{consumption_type.value}",
151-
{"dateDebut": start, "dateFin": end, "pceList[]": ",".join(pce_list)}
162+
{"dateDebut": start, "dateFin": end, "pceList[]": ",".join(pce_list)},
152163
).json()
153164

154165
if type(res) is list and len(res) == 0:
@@ -160,14 +171,21 @@ def get_pce_consumption(self, consumption_type: ConsumptionType, start_date: dat
160171
return res
161172

162173
# ------------------------------------------------------
163-
def get_pce_consumption_excelsheet(self, consumption_type: ConsumptionType, start_date: date, end_date: date, frequency: Frequency, pce_list: list[str]) -> dict[str, Any]:
174+
def get_pce_consumption_excelsheet(
175+
self,
176+
consumption_type: ConsumptionType,
177+
start_date: date,
178+
end_date: date,
179+
frequency: Frequency,
180+
pce_list: list[str],
181+
) -> dict[str, Any]:
164182

165183
start = start_date.strftime(DATE_FORMAT)
166184
end = end_date.strftime(DATE_FORMAT)
167185

168186
response = self.get(
169187
f"/e-conso/pce/consommation/{consumption_type.value}/telecharger",
170-
{"dateDebut": start, "dateFin": end, "frequence": frequency.value, "pceList[]": ",".join(pce_list)}
188+
{"dateDebut": start, "dateFin": end, "frequence": frequency.value, "pceList[]": ",".join(pce_list)},
171189
)
172190

173191
filename = response.headers["Content-Disposition"].split("filename=")[1]
@@ -181,10 +199,7 @@ def get_pce_meteo(self, end_date: date, days: int, pce: str) -> dict[str, Any]:
181199

182200
end = end_date.strftime(DATE_FORMAT)
183201

184-
res = self.get(
185-
f"/e-conso/pce/{pce}/meteo",
186-
{"dateFinPeriode": end, "nbJours": days}
187-
).json()
202+
res = self.get(f"/e-conso/pce/{pce}/meteo", {"dateFinPeriode": end, "nbJours": days}).json()
188203

189204
if type(res) is list and len(res) == 0:
190205
return dict[str, Any]()

pygazpar/datasource.py

+9-77
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,18 @@
11
import glob
2-
import http.cookiejar
32
import json
43
import logging
54
import os
6-
import time
75
from abc import ABC, abstractmethod
86
from datetime import date, timedelta
97
from typing import Any, Dict, List, Optional, cast
108

119
import pandas as pd
12-
from requests import Session
1310

11+
from pygazpar.api_client import APIClient, ConsumptionType
12+
from pygazpar.api_client import Frequency as APIClientFrequency
1413
from pygazpar.enum import Frequency, PropertyName
1514
from pygazpar.excelparser import ExcelParser
1615
from pygazpar.jsonparser import JsonParser
17-
from pygazpar.api_client import APIClient, ConsumptionType, Frequency as APIClientFrequency
18-
19-
SESSION_TOKEN_URL = "https://connexion.grdf.fr/api/v1/authn"
20-
SESSION_TOKEN_PAYLOAD = """{{
21-
"username": "{0}",
22-
"password": "{1}",
23-
"options": {{
24-
"multiOptionalFactorEnroll": "false",
25-
"warnBeforePasswordExpired": "false"
26-
}}
27-
}}"""
28-
29-
AUTH_TOKEN_URL = "https://connexion.grdf.fr/login/sessionCookieRedirect"
30-
AUTH_TOKEN_PARAMS = """{{
31-
"checkAccountSetupComplete": "true",
32-
"token": "{0}",
33-
"redirectUrl": "https://monespace.grdf.fr"
34-
}}"""
3516

3617
Logger = logging.getLogger(__name__)
3718

@@ -75,46 +56,6 @@ def load(
7556

7657
return res
7758

78-
# ------------------------------------------------------
79-
def _login(self, username: str, password: str) -> str:
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(username, password)
87-
88-
response = session.post(SESSION_TOKEN_URL, data=payload)
89-
90-
if response.status_code != 200:
91-
raise ValueError(
92-
f"An error occurred while logging in. Status code: {response.status_code} - {response.text}"
93-
)
94-
95-
session_token = response.json().get("sessionToken")
96-
97-
Logger.debug("Session token: %s", session_token)
98-
99-
jar = http.cookiejar.CookieJar()
100-
101-
self._session = Session() # pylint: disable=attribute-defined-outside-init
102-
self._session.headers.update({"Content-Type": "application/json"})
103-
self._session.headers.update({"X-Requested-With": "XMLHttpRequest"})
104-
105-
params = json.loads(AUTH_TOKEN_PARAMS.format(session_token))
106-
107-
response = self._session.get(AUTH_TOKEN_URL, params=params, allow_redirects=True, cookies=jar) # type: ignore
108-
109-
if response.status_code != 200:
110-
raise ValueError(
111-
f"An error occurred while getting the auth token. Status code: {response.status_code} - {response.text}"
112-
)
113-
114-
auth_token = self._session.cookies.get("auth_token", domain="monespace.grdf.fr")
115-
116-
return auth_token # type: ignore
117-
11859
@abstractmethod
11960
def _loadFromSession(
12061
self, pceIdentifier: str, startDate: date, endDate: date, frequencies: Optional[List[Frequency]] = None
@@ -125,8 +66,6 @@ def _loadFromSession(
12566
# ------------------------------------------------------------------------------------------------------------
12667
class ExcelWebDataSource(WebDataSource): # pylint: disable=too-few-public-methods
12768

128-
DATA_URL = "https://monespace.grdf.fr/api/e-conso/pce/consommation/informatives/telecharger?dateDebut={0}&dateFin={1}&frequence={3}&pceList[]={2}"
129-
13069
DATE_FORMAT = "%Y-%m-%d"
13170

13271
FREQUENCY_VALUES = {
@@ -173,19 +112,18 @@ def _loadFromSession( # pylint: disable=too-many-branches
173112
frequencyList = list(set(frequencies))
174113

175114
for frequency in frequencyList:
176-
# Inject parameters.
177-
downloadUrl = ExcelWebDataSource.DATA_URL.format(
178-
startDate.strftime(ExcelWebDataSource.DATE_FORMAT),
179-
endDate.strftime(ExcelWebDataSource.DATE_FORMAT),
180-
pceIdentifier,
181-
ExcelWebDataSource.FREQUENCY_VALUES[frequency],
182-
)
183115

184116
Logger.debug(
185117
f"Loading data of frequency {ExcelWebDataSource.FREQUENCY_VALUES[frequency]} from {startDate.strftime(ExcelWebDataSource.DATE_FORMAT)} to {endDate.strftime(ExcelWebDataSource.DATE_FORMAT)}"
186118
)
187119

188-
response = self._api_client.get_pce_consumption_excelsheet(ConsumptionType.INFORMATIVE, startDate, endDate, APIClientFrequency(ExcelWebDataSource.FREQUENCY_VALUES[frequency]), [pceIdentifier])
120+
response = self._api_client.get_pce_consumption_excelsheet(
121+
ConsumptionType.INFORMATIVE,
122+
startDate,
123+
endDate,
124+
APIClientFrequency(ExcelWebDataSource.FREQUENCY_VALUES[frequency]),
125+
[pceIdentifier],
126+
)
189127

190128
filename = response["filename"]
191129
content = response["content"]
@@ -249,12 +187,6 @@ def load(
249187
# ------------------------------------------------------------------------------------------------------------
250188
class JsonWebDataSource(WebDataSource): # pylint: disable=too-few-public-methods
251189

252-
DATA_URL = (
253-
"https://monespace.grdf.fr/api/e-conso/pce/consommation/informatives?dateDebut={0}&dateFin={1}&pceList[]={2}"
254-
)
255-
256-
TEMPERATURES_URL = "https://monespace.grdf.fr/api/e-conso/pce/{0}/meteo?dateFinPeriode={1}&nbJours={2}"
257-
258190
INPUT_DATE_FORMAT = "%Y-%m-%d"
259191

260192
OUTPUT_DATE_FORMAT = "%d/%m/%Y"

tests/test_api_client.py

+29-15
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
from pygazpar.api_client import APIClient, ConsumptionType, Frequency
21
import os
32
from datetime import date
3+
44
import pytest
55

6+
from pygazpar.api_client import APIClient, ConsumptionType, Frequency
7+
68

79
class TestAPIClient:
810

@@ -16,7 +18,7 @@ def setup_class(cls):
1618
cls._password = os.environ["GRDF_PASSWORD"]
1719
cls._pceIdentifier = os.environ["PCE_IDENTIFIER"]
1820

19-
cls._client = APIClient(cls._username, cls._password)
21+
cls._client = APIClient(cls._username, cls._password, 3)
2022
cls._client.login()
2123

2224
# ------------------------------------------------------
@@ -74,30 +76,42 @@ def test_get_pce_list(self):
7476
# ------------------------------------------------------
7577
def test_get_pce_consumption(self):
7678

77-
start_date = date.today()
78-
end_date = date.today()
79+
start_date = date(2025, 1, 1)
80+
end_date = date(2025, 1, 7)
7981

80-
pce_consumption_informative = TestAPIClient._client.get_pce_consumption(ConsumptionType.INFORMATIVE, start_date, end_date, [TestAPIClient._pceIdentifier])
82+
pce_consumption_informative = TestAPIClient._client.get_pce_consumption(
83+
ConsumptionType.INFORMATIVE, start_date, end_date, [TestAPIClient._pceIdentifier]
84+
)
8185

8286
assert len(pce_consumption_informative) > 0
8387

84-
pce_consumption_published = TestAPIClient._client.get_pce_consumption(ConsumptionType.PUBLISHED, start_date, end_date, [TestAPIClient._pceIdentifier])
88+
pce_consumption_published = TestAPIClient._client.get_pce_consumption(
89+
ConsumptionType.PUBLISHED, start_date, end_date, [TestAPIClient._pceIdentifier]
90+
)
8591

8692
assert len(pce_consumption_published) > 0
8793

88-
with pytest.raises(ValueError, match="An error occurred while loading data."):
89-
TestAPIClient._client.get_pce_consumption(ConsumptionType.INFORMATIVE, date(2010, 1, 1), date(2010, 1, 2), [TestAPIClient._pceIdentifier])
94+
no_result = TestAPIClient._client.get_pce_consumption(
95+
ConsumptionType.INFORMATIVE, date(2010, 1, 1), date(2010, 1, 7), [TestAPIClient._pceIdentifier]
96+
)
97+
98+
assert len(no_result) == 0
99+
100+
no_result = TestAPIClient._client.get_pce_consumption(
101+
ConsumptionType.INFORMATIVE, start_date, end_date, ["InvalidPceIdentifier"]
102+
)
90103

91-
with pytest.raises(ValueError, match="An error occurred while loading data."):
92-
TestAPIClient._client.get_pce_consumption(ConsumptionType.INFORMATIVE, start_date, end_date, ["InvalidPceIdentifier"])
104+
assert len(no_result) == 0
93105

94106
# ------------------------------------------------------
95107
def test_get_pce_consumption_excelsheet(self):
96108

97-
start_date = date.today()
98-
end_date = date.today()
109+
start_date = date(2025, 1, 1)
110+
end_date = date(2025, 1, 7)
99111

100-
pce_consumption_informative = TestAPIClient._client.get_pce_consumption_excelsheet(ConsumptionType.INFORMATIVE, start_date, end_date, Frequency.DAILY, [TestAPIClient._pceIdentifier])
112+
pce_consumption_informative = TestAPIClient._client.get_pce_consumption_excelsheet(
113+
ConsumptionType.INFORMATIVE, start_date, end_date, Frequency.DAILY, [TestAPIClient._pceIdentifier]
114+
)
101115

102116
assert len(pce_consumption_informative) > 0
103117

@@ -110,9 +124,9 @@ def test_get_pce_meteo(self):
110124

111125
assert len(pce_meteo) > 0
112126

113-
pce_meteo_no_result = TestAPIClient._client.get_pce_meteo(date(2010, 1, 2), 2, TestAPIClient._pceIdentifier)
127+
pce_meteo_no_result = TestAPIClient._client.get_pce_meteo(date(2010, 1, 2), 7, TestAPIClient._pceIdentifier)
114128

115129
assert len(pce_meteo_no_result) == 0
116130

117131
with pytest.raises(ValueError, match="Le pce InvalidPceIdentifier n'existe pas !"):
118-
TestAPIClient._client.get_pce_meteo(end_date, 2, "InvalidPceIdentifier")
132+
TestAPIClient._client.get_pce_meteo(end_date, 7, "InvalidPceIdentifier")

0 commit comments

Comments
 (0)