pygazpar 0.1.21__py3-none-any.whl → 1.3.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
pygazpar/client.py CHANGED
@@ -1,210 +1,98 @@
1
- import os
2
- import time
3
- import glob
4
- import logging
5
- from datetime import datetime
6
- from openpyxl import load_workbook
7
- from pygazpar.enum import PropertyNameEnum
8
- from .webdriverwrapper import WebDriverWrapper
9
-
10
- HOME_URL = 'https://monespace.grdf.fr'
11
- LOGIN_URL = HOME_URL + '/monespace/connexion'
12
- WELCOME_URL = HOME_URL + '/monespace/particulier/accueil'
13
- DATA_URL = HOME_URL + '/monespace/particulier/consommation/consommations'
14
- DATA_FILENAME = 'Consommations gaz_*.xlsx'
15
-
16
- DEFAULT_TMP_DIRECTORY = '/tmp'
17
- DEFAULT_FIREFOX_WEBDRIVER = os.getcwd() + '/geckodriver'
18
- DEFAULT_WAIT_TIME = 30
19
-
20
- # ------------------------------------------------------------------------------------------------------------
21
- class LoginError(Exception):
22
- """ Client has failed to login in GrDF Web site (check username/password)"""
23
- pass
24
-
25
-
26
- # ------------------------------------------------------------------------------------------------------------
27
- class Client(object):
28
-
29
- logger = logging.getLogger(__name__)
30
-
31
- # ------------------------------------------------------
32
- def __init__(self, username: str, password: str, firefox_webdriver_executable: str = DEFAULT_FIREFOX_WEBDRIVER, wait_time: int = DEFAULT_WAIT_TIME, tmp_directory: str = DEFAULT_TMP_DIRECTORY, lastNRows: int = 0):
33
- self.__username = username
34
- self.__password = password
35
- self.__firefox_webdriver_executable = firefox_webdriver_executable
36
- self.__wait_time = wait_time
37
- self.__tmp_directory = tmp_directory
38
- self.__data = []
39
- self.__lastNRows = lastNRows
40
-
41
-
42
- # ------------------------------------------------------
43
- def data(self):
44
- return self.__data
45
-
46
-
47
- # ------------------------------------------------------
48
- def acceptCookies(self, driver: WebDriverWrapper):
49
-
50
- try:
51
- cookies_accept_button = driver.find_element_by_xpath("//a[@id='_EPcommonPage_WAR_EPportlet_:formBandeauCnil:j_idt12']", "Cookies accept button", False)
52
- cookies_accept_button.click()
53
- except:
54
- # Do nothing, because the Pop up may not appear.
55
- pass
56
-
57
-
58
- # ------------------------------------------------------
59
- def acceptPrivacyConditions(self, driver: WebDriverWrapper):
60
-
61
- try:
62
- # id=btn_accept_banner
63
- accept_button = driver.find_element_by_id("btn_accept_banner", "Privacy Conditions accept button", False)
64
- accept_button.click()
65
- except:
66
- # Do nothing, because the Pop up may not appear.
67
- pass
68
-
69
-
70
- # ------------------------------------------------------
71
- def closeEventualPopup(self, driver: WebDriverWrapper):
72
-
73
- # Accept an eventual Privacy Conditions popup.
74
- self.acceptPrivacyConditions(driver)
75
-
76
- # Eventually, click Accept in the lower banner to accept cookies from the site.
77
- self.acceptCookies(driver)
78
-
79
- # Eventually, close Advertisement Popup Windows.
80
- try:
81
- advertisement_popup_element = driver.find_element_by_xpath("/html/body/abtasty-modal/div/div[1]", "Advertisement close button", False)
82
- advertisement_popup_element.click()
83
- except:
84
- # Do nothing, because the Pop up may not appear.
85
- pass
86
-
87
- # Eventually, close Survey Popup Windows : /html/body/div[12]/div[2] or //*[@id="mfbIframeClose"]
88
- try:
89
- survey_popup_element = driver.find_element_by_xpath("//*[@id='mfbIframeClose']", "Survey close button", False)
90
- survey_popup_element.click()
91
- except:
92
- # Do nothing, because the Pop up may not appear.
93
- pass
94
-
95
-
96
- # ------------------------------------------------------
97
- def update(self):
98
-
99
- Client.logger.debug("Start updating the data...")
100
-
101
- # XLSX is in the TMP directory
102
- data_file_path_pattern = self.__tmp_directory + '/' + DATA_FILENAME
103
-
104
- # We remove an eventual existing data file (from a previous run that has not deleted it)
105
- file_list = glob.glob(data_file_path_pattern)
106
- for filename in file_list:
107
- if os.path.isfile(filename):
108
- os.remove(filename)
109
-
110
- # Create the WebDriver with the ability to log and take screenshot for debugging.
111
- driver = WebDriverWrapper(self.__firefox_webdriver_executable, self.__wait_time, self.__tmp_directory)
112
-
113
- try:
114
-
115
- ## Login URL
116
- driver.get(LOGIN_URL, "Go to login page")
117
-
118
- # Fill login form
119
- email_element = driver.find_element_by_id("_EspacePerso_WAR_EPportlet_:seConnecterForm:email", "Login page: Email text field")
120
- password_element = driver.find_element_by_id("_EspacePerso_WAR_EPportlet_:seConnecterForm:passwordSecretSeConnecter", "Login page: Password text field")
121
-
122
- email_element.send_keys(self.__username)
123
- password_element.send_keys(self.__password)
124
-
125
- # Submit the login form.
126
- submit_button_element = driver.find_element_by_id("_EspacePerso_WAR_EPportlet_:seConnecterForm:meConnecter", "Login page: 'Me connecter' button")
127
- submit_button_element.click()
128
-
129
- # Close eventual popup Windows or Assistant appearing.
130
- self.closeEventualPopup(driver)
131
-
132
- # Once we find the 'Acceder' button from the main page, we are logged on successfully.
133
- try:
134
- driver.find_element_by_xpath("//div[2]/div[2]/div/a/div", "Welcome page: 'Acceder' button of 'Suivi de consommation'")
135
- except:
136
- # Perhaps, login has failed.
137
- if driver.current_url() == WELCOME_URL:
138
- # We're good.
139
- pass
140
- elif driver.current_url() == LOGIN_URL:
141
- raise LoginError("GrDF sign in has failed, please check your username/password")
142
- else:
143
- raise
144
-
145
- # Page 'votre consommation'
146
- driver.get(DATA_URL, "Go to 'Consommations' page")
147
-
148
- # Wait for the data page to load completely.
149
- time.sleep(self.__wait_time)
150
-
151
- # Eventually, close TokyWoky assistant which may hide the Download button.
152
- try:
153
- tokyWoky_close_button = driver.find_element_by_xpath("//div[@id='toky_container']/div/div", "TokyWoky assistant close button")
154
- tokyWoky_close_button.click()
155
- except:
156
- # Do nothing, because the Pop up may not appear.
157
- pass
158
-
159
- # Select daily consumption
160
- daily_consumption_element = driver.find_element_by_xpath("//table[@id='_eConsoconsoDetaille_WAR_eConsoportlet_:idFormConsoDetaille:panelTypeGranularite1']/tbody/tr/td[3]/label", "Daily consumption button")
161
- daily_consumption_element.click()
162
-
163
- # Download file
164
- # xpath=//button[@id='_eConsoconsoDetaille_WAR_eConsoportlet_:idFormConsoDetaille:telechargerDonnees']/span
165
- download_button_element = driver.find_element_by_xpath("//button[@onclick=\"envoieGATelechargerConsoDetaille('particulier', 'jour_kwh');\"]/span", "Download button")
166
- download_button_element.click()
167
-
168
- # Timestamp of the data.
169
- data_timestamp = datetime.now().isoformat()
170
-
171
- # Wait a few for the download to complete
172
- time.sleep(self.__wait_time)
173
-
174
- # Load the XLSX file into the data structure
175
- file_list = glob.glob(data_file_path_pattern)
176
-
177
- for filename in file_list:
178
-
179
- Client.logger.debug(f"Loading Excel data file '{filename}'...")
180
- wb = load_workbook(filename = filename)
181
- ws = wb['Historique par jour']
182
- minRowNum = max(8, len(ws['B'])+1-self.__lastNRows) if self.__lastNRows > 0 else 8
183
- maxRowNum = len(ws['B'])
184
- for rownum in range(minRowNum, maxRowNum + 1):
185
- row = {}
186
- if ws.cell(column=2, row=rownum).value != None:
187
- row[PropertyNameEnum.DATE.value] = ws.cell(column=2, row=rownum).value
188
- row[PropertyNameEnum.START_INDEX_M3.value] = ws.cell(column=3, row=rownum).value
189
- row[PropertyNameEnum.END_INDEX_M3.value] = ws.cell(column=4, row=rownum).value
190
- row[PropertyNameEnum.VOLUME_M3.value] = ws.cell(column=5, row=rownum).value
191
- row[PropertyNameEnum.ENERGY_KWH.value] = ws.cell(column=6, row=rownum).value
192
- row[PropertyNameEnum.CONVERTER_FACTOR.value] = ws.cell(column=7, row=rownum).value
193
- row[PropertyNameEnum.LOCAL_TEMPERATURE.value] = ws.cell(column=8, row=rownum).value
194
- row[PropertyNameEnum.TYPE.value] = ws.cell(column=9, row=rownum).value
195
- row[PropertyNameEnum.TIMESTAMP.value] = data_timestamp
196
- self.__data.append(row)
197
- wb.close()
198
- Client.logger.debug(f"Data read successfully between row #{minRowNum} and row #{maxRowNum}")
199
-
200
- os.remove(filename)
201
-
202
- Client.logger.debug("The data update terminates normally")
203
-
204
-
205
- except Exception:
206
- WebDriverWrapper.logger.error(f"An unexpected error occured while updating the data", exc_info=True)
207
- finally:
208
- # Quit the driver
209
- driver.quit()
210
-
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)