pygazpar 0.1.21__py3-none-any.whl → 1.3.0__py3-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/__init__.py +10 -3
- pygazpar/__main__.py +89 -58
- pygazpar/api_client.py +228 -0
- pygazpar/client.py +98 -210
- pygazpar/datasource.py +590 -0
- pygazpar/enum.py +31 -8
- pygazpar/excelparser.py +138 -0
- pygazpar/jsonparser.py +53 -0
- pygazpar/resources/daily_data_sample.json +7802 -0
- pygazpar/resources/hourly_data_sample.json +1 -0
- pygazpar/resources/monthly_data_sample.json +146 -0
- pygazpar/resources/weekly_data_sample.json +614 -0
- pygazpar/resources/yearly_data_sample.json +18 -0
- pygazpar/version.py +3 -0
- pygazpar-0.1.21.dist-info/LICENSE.txt → pygazpar-1.3.0.dist-info/LICENSE +21 -21
- pygazpar-1.3.0.dist-info/METADATA +225 -0
- pygazpar-1.3.0.dist-info/RECORD +18 -0
- {pygazpar-0.1.21.dist-info → pygazpar-1.3.0.dist-info}/WHEEL +1 -2
- pygazpar/webdriverwrapper.py +0 -125
- pygazpar/webelementwrapper.py +0 -40
- pygazpar-0.1.21.dist-info/METADATA +0 -149
- pygazpar-0.1.21.dist-info/RECORD +0 -14
- pygazpar-0.1.21.dist-info/entry_points.txt +0 -3
- pygazpar-0.1.21.dist-info/top_level.txt +0 -2
- test/__init__.py +0 -1
- test/test_client.py +0 -50
pygazpar/client.py
CHANGED
@@ -1,210 +1,98 @@
|
|
1
|
-
import
|
2
|
-
import
|
3
|
-
import
|
4
|
-
import
|
5
|
-
|
6
|
-
from
|
7
|
-
from pygazpar.enum import
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
# ------------------------------------------------------
|
32
|
-
def
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
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)
|