Hydro-Quebec-API-Wrapper 3.2.0__py3-none-any.whl → 4.0.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: Hydro-Quebec-API-Wrapper
3
- Version: 3.2.0
3
+ Version: 4.0.0
4
4
  Summary: A wrapper library to access hydro quebec API and more
5
5
  Home-page: https://hydroqc.ca
6
6
  Author-email: Hydroqc Team <info@hydroqc.ca>
@@ -15,11 +15,11 @@ Classifier: Operating System :: OS Independent
15
15
  Requires-Python: >=3.10.0
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE
18
- Requires-Dist: aiohttp ==3.9.5
19
- Requires-Dist: python-dateutil ==2.9.0.post0
20
- Requires-Dist: pytz ==2024.1
21
- Requires-Dist: aiocache ==0.12.2
22
- Requires-Dist: pkce ==1.0.3
18
+ Requires-Dist: aiohttp==3.10.10
19
+ Requires-Dist: python-dateutil==2.9.0.post0
20
+ Requires-Dist: pytz==2024.2
21
+ Requires-Dist: aiocache==0.12.3
22
+ Requires-Dist: pkce==1.0.3
23
23
 
24
24
  # HydroQC - Hydro Quebec API wrapper
25
25
 
@@ -2,13 +2,14 @@ hydroqc/__init__.py,sha256=sFhCh7-LWoISCQuAtdrLQOmj2x5wjHBGbAAPg4SN6Pc,40
2
2
  hydroqc/account.py,sha256=TVohNCLYL5BRHbzQn0KE_cBsMJva9hZ8rLva8rHP5CI,3364
3
3
  hydroqc/consts.py,sha256=a9dWq5i6t5VedYosUbP8zIuG2q5TPw4ruT0jOaC8Yo8,209
4
4
  hydroqc/customer.py,sha256=SdpVdB_Kwo3tZftJhr6N2weNiCF4Bl005PnxORN0xGc,6166
5
- hydroqc/diagnostic.py,sha256=H7JvpCdG_nWKk8K5QFVSimqilyiU9l9IWyuL4atcaJE,15517
5
+ hydroqc/diagnostic.py,sha256=eVY3HfMcLCjTbKWo9xdaw14ygju4qUIKy9pxcQstoRc,17322
6
6
  hydroqc/error.py,sha256=2HzWBak_RpuhfIrLhnfgNuY3FhUwwNyo1BLUDH6U0CI,598
7
7
  hydroqc/logger.py,sha256=Fez6CqjBBxd841U3avtnrT3E43Vyy-v1e7hqwUmf9bg,1256
8
+ hydroqc/public_client.py,sha256=9NCwVIyrM2BQtGbXXi28owPUhFIVpSWXj66ysCohyhQ,2860
8
9
  hydroqc/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
10
  hydroqc/timerange.py,sha256=MXR9yau72m4UZBSmN1qB0jMs0x4_jEZYnwtaUnGq-MQ,1137
10
11
  hydroqc/utils.py,sha256=sxA5qkOA-Ui5jgEYItFQIeQdBsS_5z8J3xn8YxKZMow,232
11
- hydroqc/webuser.py,sha256=iQXkJ95iCSUZ2BogLBOeqZuJKv4kiJDVOlg_h5HNVzg,4411
12
+ hydroqc/webuser.py,sha256=Nm1cy1zD7MfEWX1vum4ziXzA579YCO3lenPldcY2-z4,4518
12
13
  hydroqc/contract/__init__.py,sha256=uqxjOgyhpKFd-n_umxdaW5FB_DkJJaettWnEFC0nRYc,548
13
14
  hydroqc/contract/common.py,sha256=uQ7NuLsPNo64cHli7UcpMWR0UPsy-fFLwdTvL0EnrNM,15446
14
15
  hydroqc/contract/contract_d.py,sha256=nAg1gyDl6l8iU7BOpaXCGMm6dsmHgCpnZqAL0yWSxRU,799
@@ -21,29 +22,32 @@ hydroqc/contract/contract_residential.py,sha256=I5cIVii5TMqcsROUx5cGqnwuJULk8AN8
21
22
  hydroqc/contract/outage.py,sha256=K5XX0enSKMVpL-JCkWsykuKUw-5_GcSJLzcSR1SqQDk,3979
22
23
  hydroqc/hydro_api/__init__.py,sha256=bIIjTkhQcXG7kn4cQr_Q6tFSKGjAx-AXLjMMlRCjclY,32
23
24
  hydroqc/hydro_api/cache.py,sha256=mC3UO246V5zJ1p89nUb5EzQon_4tQGUgFMZKy4YY8I8,912
24
- hydroqc/hydro_api/client.py,sha256=Q4JlX8oa5j_MEMsp5CrM6SY5-tUeHGW9-X2jnt47l0I,33956
25
- hydroqc/hydro_api/consts.py,sha256=nqbGnhIMZzPmAa7jvm7Vzz0Ay7VglZ4Bwek08Z1_MtY,4450
25
+ hydroqc/hydro_api/client.py,sha256=1mB8PRam-ZvAlAxUFgLMkRihhuLGT198qASSYVAABiU,35172
26
+ hydroqc/hydro_api/consts.py,sha256=lLNlVgWZ21ExG6S_UZVeHcczFpm2YZMAOZRqzEy67c8,4632
26
27
  hydroqc/peak/__init__.py,sha256=ye2KbWXhJEHZnBDaFylWioy3W-mzJw_lDplsJNAapNs,19
28
+ hydroqc/peak/basepeak.py,sha256=DMXjZXyNRHWnuhB0Vp3ZINGhNQngG7AU15cDLajmC7M,1954
29
+ hydroqc/peak/basepeakhandler.py,sha256=Bfg1B4DSWDZFk0WAyHVJibXw_ZAH7x4eaAVvZy7Y-cI,9173
30
+ hydroqc/peak/consts.py,sha256=2gUTZcDzu6Dia5g5w7-IG0JFt-9YdVopYcbHXjW3EK8,67
27
31
  hydroqc/peak/cpc/__init__.py,sha256=sez6LQOUqDu6YVIpJexq9wYv3wef3I4TeCxwHhO9bBU,18
28
32
  hydroqc/peak/cpc/consts.py,sha256=75h23l1cdwOZm0wn5CTWIU98grDqplv8_8tnpbjnlYM,544
29
- hydroqc/peak/cpc/handler.py,sha256=Zr1aHZEaSQHdmQOcCgHB_dJ5Z0UYyyaZJDyKP0dsnoo,15696
30
- hydroqc/peak/cpc/peak.py,sha256=3DVNKgUeq7yX9Y_Mm2tpstPTJJhbmIUSkgwGvS613PE,6701
33
+ hydroqc/peak/cpc/handler.py,sha256=cxelJ0aYnzQN-NxoNWSIpd5GaoBw7w9cKVCKOg_fYAQ,12389
34
+ hydroqc/peak/cpc/peak.py,sha256=8U2XibVs7NTT3OBzZAW_fyfVDEN68P98YtqVZh5f5BE,4266
31
35
  hydroqc/peak/dpc/__init__.py,sha256=H99D93OC4quEag-lQZ3BEF2mr5Qiu3DtvdKjBsiSRBg,18
32
- hydroqc/peak/dpc/consts.py,sha256=Y3HS7YB-LsjXKJoo8FxTV_5lCs-PcaglLpBgopc4AF8,555
33
- hydroqc/peak/dpc/handler.py,sha256=F5enMUxKARk7GnZEMf-zhxhmI88lA9Ex5uqvR4QUfqY,8861
34
- hydroqc/peak/dpc/peak.py,sha256=MgqNSAbK8Nm-SbGVKGPfXHw1vI8pVOD4-uubgLhrwyQ,1794
35
- hydroqc/types/__init__.py,sha256=wQppiQPM3ekYm6k-A0AJPH3cELfsy2f-4dmYvrjthz0,1334
36
+ hydroqc/peak/dpc/consts.py,sha256=iRrEY_nsxb71AqU9KY7MRVkwxQM0ofBeEJLdyrjnhOc,523
37
+ hydroqc/peak/dpc/handler.py,sha256=J98bVzHMqqTsUvnGcnTXLBXI95FgA8FWSRonXZS7xmg,3365
38
+ hydroqc/peak/dpc/peak.py,sha256=clb1j6LaBndPo0qPlOxumGuUGYB4-_ob_s8UQa-m49o,1313
39
+ hydroqc/types/__init__.py,sha256=1dzth61xIsdteCEy2oVaavT78_djw-lUpzpXPRzQE40,1453
36
40
  hydroqc/types/account.py,sha256=yXwqjU3lBG7cGt8RF0cyvNSxtBCaIoWgmtvsLisvQTY,3696
37
- hydroqc/types/common.py,sha256=U9u79GpsmZkJ41rchtUOE7RqTxQvw0OQW1G2bFT9pN8,816
41
+ hydroqc/types/common.py,sha256=4uZRo4JDoVPmhugLUcA9eh_6xkzn_hNvJdRO-viDKQA,1395
38
42
  hydroqc/types/consump.py,sha256=-MIF6gZrOBI6ng6edBcI41sD5z6nDF3X3Kvoq-EjSoM,5929
39
43
  hydroqc/types/contract.py,sha256=OTi8c5CEces3vwwQLbPzo1jB7sgwOweS1imFPnJMML0,1657
40
44
  hydroqc/types/cpc.py,sha256=ghmtBTT1FKuqVodJJ0kCtUbYNTLCOrngxgcrzjJYvok,2116
41
45
  hydroqc/types/dpc.py,sha256=5MGf3i74s6r4gJpFjNSgbN1v85VZO7dtu7pB5B4sgUQ,1328
42
46
  hydroqc/types/dt.py,sha256=GzIY_5Bc-bVjYURN3O8Z3xVd9Y24mWYirSwBMhZjbPk,329
43
47
  hydroqc/types/outage.py,sha256=XTsNyu8mmxUx44XWvOOUHKSoijp-lgbXwQroiqm9S0o,3932
44
- Hydro_Quebec_API_Wrapper-3.2.0.dist-info/LICENSE,sha256=46mU2C5kSwOnkqkw9XQAJlhBL2JAf1_uCD8lVcXyMRg,7652
45
- Hydro_Quebec_API_Wrapper-3.2.0.dist-info/METADATA,sha256=iCTFBbRmPktGdSCONf77J_v9dC7ZqIhDTl9UCtQszSE,7930
46
- Hydro_Quebec_API_Wrapper-3.2.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
47
- Hydro_Quebec_API_Wrapper-3.2.0.dist-info/entry_points.txt,sha256=G2YKkCH9rFt2dXbFyHINBp4bmqY2HTEIaf7F1CwbC_k,57
48
- Hydro_Quebec_API_Wrapper-3.2.0.dist-info/top_level.txt,sha256=YAcYLnu5YYkbxt1psUoBfo_opl-lAZKq0A7B3IN_P6M,8
49
- Hydro_Quebec_API_Wrapper-3.2.0.dist-info/RECORD,,
48
+ Hydro_Quebec_API_Wrapper-4.0.0.dist-info/LICENSE,sha256=46mU2C5kSwOnkqkw9XQAJlhBL2JAf1_uCD8lVcXyMRg,7652
49
+ Hydro_Quebec_API_Wrapper-4.0.0.dist-info/METADATA,sha256=tjqmlbvqN4AjB_MzS5gl0RMojBsBmEnlluJpmh2_i4c,7927
50
+ Hydro_Quebec_API_Wrapper-4.0.0.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
51
+ Hydro_Quebec_API_Wrapper-4.0.0.dist-info/entry_points.txt,sha256=G2YKkCH9rFt2dXbFyHINBp4bmqY2HTEIaf7F1CwbC_k,57
52
+ Hydro_Quebec_API_Wrapper-4.0.0.dist-info/top_level.txt,sha256=YAcYLnu5YYkbxt1psUoBfo_opl-lAZKq0A7B3IN_P6M,8
53
+ Hydro_Quebec_API_Wrapper-4.0.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.43.0)
2
+ Generator: setuptools (75.3.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
hydroqc/diagnostic.py CHANGED
@@ -20,6 +20,7 @@ from dateutil.relativedelta import relativedelta
20
20
  from hydroqc.contract import ContractDCPC, ContractDPC, ContractM
21
21
  from hydroqc.error import HydroQcError
22
22
  from hydroqc.logger import get_logger
23
+ from hydroqc.public_client import PublicClient
23
24
  from hydroqc.webuser import WebUser
24
25
 
25
26
  LOG_LEVEL = (
@@ -39,10 +40,12 @@ class Diagnostic:
39
40
  """Contructor."""
40
41
  # Get base settings
41
42
  self.diagnostic_folder = cli_settings.output_folder
43
+ self.diagnostic_folder_public = os.path.join(self.diagnostic_folder, "public")
42
44
  # Prepare output folder
43
45
  if os.path.exists(self.diagnostic_folder):
44
46
  shutil.rmtree(self.diagnostic_folder)
45
47
  os.makedirs(self.diagnostic_folder)
48
+ os.makedirs(self.diagnostic_folder_public)
46
49
  # Prepare logger
47
50
  self._log_file = os.path.join(self.diagnostic_folder, "diagnostic.log")
48
51
  log_level = cli_settings.log_level
@@ -51,6 +54,7 @@ class Diagnostic:
51
54
  file_handler.setFormatter(self.logger.handlers[0].formatter)
52
55
  file_handler.setLevel(self.logger.level)
53
56
  self.logger.addHandler(file_handler)
57
+ self.logger_public = self.logger.getChild("public")
54
58
  http_log_level = cli_settings.http_log_level
55
59
 
56
60
  # Get settings
@@ -69,6 +73,15 @@ class Diagnostic:
69
73
  log_file=self._log_file,
70
74
  diag_folder=self.diagnostic_folder,
71
75
  )
76
+ self.public_client = PublicClient(
77
+ rate_code="DPC",
78
+ # rate_option_code="CPC",
79
+ verify_ssl=True,
80
+ log_level=log_level,
81
+ http_log_level=http_log_level,
82
+ log_file=self._log_file,
83
+ diag_folder=self.diagnostic_folder_public,
84
+ )
72
85
 
73
86
  def zip(self) -> None:
74
87
  """Archive all generated files."""
@@ -87,6 +100,11 @@ class Diagnostic:
87
100
  """Get diagnostic http ID."""
88
101
  return self.webuser._hydro_client._diag_id
89
102
 
103
+ @property
104
+ def public_diag_id(self) -> int:
105
+ """Get diagnostic http ID."""
106
+ return self.public_client._hydro_client._diag_id
107
+
90
108
  async def run(self) -> None: # pylint: disable=too-many-statements
91
109
  """Run diagnostic."""
92
110
  today = datetime.date.today()
@@ -94,6 +112,24 @@ class Diagnostic:
94
112
  day_2 = today - datetime.timedelta(days=2)
95
113
 
96
114
  try:
115
+ self.logger_public.info(
116
+ "%02d - Checking HQ portal status", self.public_diag_id
117
+ )
118
+ hq_status = await self.public_client.check_hq_portal_status()
119
+ self.logger_public.info(
120
+ "%02d - HQ portal available: %s", self.public_diag_id - 1, hq_status
121
+ )
122
+
123
+ self.logger_public.info(
124
+ "%02d - Fetch peak using open data", self.public_diag_id
125
+ )
126
+ await self.public_client.fetch_peak_data()
127
+ self.logger_public.info(
128
+ "%02d - Peak found: %s",
129
+ self.public_diag_id - 1,
130
+ len(self.public_client.peaks),
131
+ )
132
+
97
133
  self.logger.info("%02d - Checking HQ portal status", self.diag_id)
98
134
  hq_status = await self.webuser.check_hq_portal_status()
99
135
  self.logger.info(
@@ -267,6 +303,10 @@ class Diagnostic:
267
303
  await contract_dcpc.peak_handler.refresh_data()
268
304
  self.logger.info("%02d - Got cpc data", self.diag_id - 1)
269
305
 
306
+ self.logger.info("%02d - Trying to get opn data", self.diag_id)
307
+ await contract_dcpc.peak_handler.refresh_open_data()
308
+ self.logger.info("%02d - Got open data", self.diag_id - 1)
309
+
270
310
  # DCP/FlexD
271
311
  elif self.contract.rate == "DPC":
272
312
  self.logger.info("%02d - Trying to get flexD data", self.diag_id)
@@ -289,6 +329,10 @@ class Diagnostic:
289
329
  await contract_dpc.peak_handler.refresh_data()
290
330
  self.logger.info("%02d - Got flexD data", self.diag_id - 1)
291
331
 
332
+ self.logger.info("%02d - Trying to get opn data", self.diag_id)
333
+ await contract_dcpc.peak_handler.refresh_open_data()
334
+ self.logger.info("%02d - Got open data", self.diag_id - 1)
335
+
292
336
  # DT
293
337
  if self.contract.rate == "DT":
294
338
  low_price = contract_dpc.cp_lower_price_consumption
@@ -330,6 +374,9 @@ class Diagnostic:
330
374
  traceback.print_exception(exp, file=fhtb)
331
375
  fhtb.seek(0)
332
376
  self.logger.error(fhtb.read())
377
+ finally:
378
+ await self.webuser.close_session()
379
+ await self.public_client.close_session()
333
380
 
334
381
 
335
382
  def parse_args() -> argparse.Namespace:
@@ -380,14 +427,10 @@ def main() -> None:
380
427
  """Run diagnostic."""
381
428
  cli_settings = parse_args()
382
429
 
383
- loop = asyncio.get_event_loop()
384
430
  diag = Diagnostic(cli_settings)
385
431
 
386
432
  # Fetch data
387
- loop.run_until_complete(diag.run())
388
- close_fut = asyncio.gather(diag.webuser.close_session())
389
- loop.run_until_complete(close_fut)
390
- loop.close()
433
+ asyncio.run(diag.run())
391
434
  # zip
392
435
  diag.zip()
393
436
 
@@ -14,11 +14,11 @@ from datetime import date, datetime, timedelta
14
14
  from importlib.metadata import PackageNotFoundError, version
15
15
  from io import StringIO
16
16
  from json import dumps as json_dumps
17
- from typing import Any
18
- from pkce import generate_pkce_pair
17
+ from typing import Any, cast
19
18
 
20
19
  import aiohttp
21
20
  import yarl
21
+ from pkce import generate_pkce_pair
22
22
 
23
23
  from hydroqc.error import HydroQcError, HydroQcHTTPError
24
24
  from hydroqc.hydro_api.cache import CCached
@@ -43,6 +43,7 @@ from hydroqc.hydro_api.consts import (
43
43
  HOURLY_CONSUMPTION_API_URL,
44
44
  IS_HYDRO_PORTAL_UP_URL,
45
45
  MONTHLY_DATA_URL,
46
+ OPEN_DATA_PEAK_URL,
46
47
  OUTAGES,
47
48
  PERIOD_DATA_URL,
48
49
  PORTRAIT_URL,
@@ -67,6 +68,8 @@ from hydroqc.types import (
67
68
  InfoAccountTyping,
68
69
  ListAccountsContractsTyping,
69
70
  ListContractsTyping,
71
+ OpenDataPeakEvent,
72
+ OpenDataPeakEventOffer,
70
73
  OutageListTyping,
71
74
  PeriodDataTyping,
72
75
  )
@@ -76,6 +79,8 @@ from hydroqc.utils import EST_TIMEZONE
76
79
  class HydroClient:
77
80
  """HydroQc HTTP Client."""
78
81
 
82
+ _cookie_jar: aiohttp.CookieJar
83
+
79
84
  def __init__(
80
85
  self,
81
86
  username: str,
@@ -91,7 +96,6 @@ class HydroClient:
91
96
  self.password: str = password
92
97
  self._timeout: int = timeout
93
98
  self._session: aiohttp.ClientSession | None = session
94
- self._cookie_jar: aiohttp.CookieJar = aiohttp.CookieJar()
95
99
  self._verify_ssl: bool = verify_ssl
96
100
 
97
101
  # OAuth handling variables
@@ -119,7 +123,7 @@ class HydroClient:
119
123
  self.refresh_token = ""
120
124
  self.refresh_token_expiry = datetime.now()
121
125
  self.web_session_expiry = datetime.now()
122
- self._cookie_jar = aiohttp.CookieJar()
126
+ # self._cookie_jar = aiohttp.CookieJar()
123
127
  self._selected_customer = ""
124
128
  self._selected_contract = ""
125
129
 
@@ -234,16 +238,20 @@ class HydroClient:
234
238
  self._logger.debug("JSON received : ", data)
235
239
  raise HydroQcHTTPError("Bad JSON format") from exp
236
240
 
237
- def get_token_data(self) -> IDTokenTyping:
241
+ def get_token_data(self) -> IDTokenTyping | None:
238
242
  """Decode id token data."""
243
+ if not self._id_token:
244
+ return None
239
245
  raw_token_data = self._id_token.split(".")[1]
240
246
  # In some cases padding get lost, adding it to avoid issues with base64 decode
241
247
  raw_token_data += "=" * ((4 - len(raw_token_data) % 4) % 4)
242
248
  token_data: IDTokenTyping = self._load_json(base64.b64decode(raw_token_data))
243
249
  return token_data
244
250
 
245
- def _get_httpsession(self) -> None:
251
+ async def _get_httpsession(self) -> None:
246
252
  """Set http session."""
253
+ if not hasattr(self, "_cookie_jar"):
254
+ self._cookie_jar = aiohttp.CookieJar()
247
255
  if self._session is None:
248
256
  self._logger.debug("Creating new http session")
249
257
  self._session = aiohttp.ClientSession(
@@ -276,7 +284,7 @@ class HydroClient:
276
284
  async def check_portal_status(self) -> bool:
277
285
  """Check if Hydro Quebec portal is UP."""
278
286
  # Get http session
279
- self._get_httpsession()
287
+ await self._get_httpsession()
280
288
  self._logger.info("Checking if the Hydro Quebec portal is UP")
281
289
  api_call_response = await self.http_request(
282
290
  IS_HYDRO_PORTAL_UP_URL,
@@ -301,7 +309,7 @@ class HydroClient:
301
309
  code_verifier, code_challenge = generate_pkce_pair()
302
310
 
303
311
  # Get http session
304
- self._get_httpsession()
312
+ await self._get_httpsession()
305
313
 
306
314
  url = f"{AUTHORIZE_URL}"
307
315
  headers = {
@@ -912,3 +920,27 @@ class HydroClient:
912
920
  return content
913
921
  data_csv: Iterator[Any] = csv.reader(content, delimiter=";")
914
922
  return data_csv
923
+
924
+ @CCached(ttl=300)
925
+ async def get_open_data_peaks(
926
+ self,
927
+ offer: OpenDataPeakEventOffer | None = None,
928
+ ) -> list[OpenDataPeakEvent]:
929
+ """Get the list of peak event from open data url."""
930
+ await self._get_httpsession()
931
+ res = await self.http_request(OPEN_DATA_PEAK_URL, "get")
932
+ content = await res.text()
933
+ try:
934
+ raw_res_json = json.loads(content)
935
+ except json.JSONDecodeError:
936
+ # Try to clean up json removing comments
937
+ cleaned_json = ""
938
+ res_text = await res.text()
939
+ for line in res_text.splitlines():
940
+ if not line.startswith("/") and not line.startswith("#"):
941
+ cleaned_json += line
942
+ raw_res_json = json.loads(cleaned_json)
943
+ res_json = cast(list[OpenDataPeakEvent], raw_res_json.get("evenements", []))
944
+ if not offer:
945
+ return res_json
946
+ return [e for e in res_json if e["offre"] == offer]
@@ -15,6 +15,7 @@ HOST_SERVICES = "https://services-cl.solutions.hydroquebec.com"
15
15
  # HOST_SPRING = "https://cl-ec-lsw.solutions.hydroquebec.com"
16
16
  HOST_RB_SOL = "https://rb.solutions.hydroquebec.com"
17
17
  HOST_OUTAGES = "https://services-bs.solutions.hydroquebec.com"
18
+ HOST_OPEN_DATA = "https://donnees.solutions.hydroquebec.com"
18
19
 
19
20
  # Azure B2C
20
21
  AZB2C_TENANT_ID = "32bf9b91-0a36-4385-b231-d9a8fa3b05ab"
@@ -47,6 +48,11 @@ SECURITY_URL = f"{HOST_SESSION}/config/security.json"
47
48
  SESSION_REFRESH_URL = f"{HOST_SESSION}/oauth2/callback/silent-refresh"
48
49
  LOGIN_URL_6 = f"{HOST_SERVICES}/wsapi/web/prive/api/v3_0/conversion/codeAcces"
49
50
 
51
+ # OPEN DATA PEAK URL
52
+ OPEN_DATA_PEAK_URL = (
53
+ f"{HOST_OPEN_DATA}/donnees-ouvertes/data/json/pointeshivernales.json"
54
+ )
55
+
50
56
  # Initialization uri
51
57
  RELATION_URL = f"{HOST_SERVICES}/wsapi/web/prive/api/v1_0/relations"
52
58
 
@@ -0,0 +1,67 @@
1
+ """Base Peak module."""
2
+
3
+ import datetime
4
+
5
+ from hydroqc.error import HydroQcCPCPeakError
6
+ from hydroqc.timerange import TimeRange
7
+ from hydroqc.utils import EST_TIMEZONE
8
+
9
+
10
+ class BasePeak(TimeRange):
11
+ """Base peak class."""
12
+
13
+ _start_date: datetime.datetime
14
+ _end_date: datetime.datetime
15
+
16
+ def __init__(self, start_date: datetime.datetime, end_date: datetime.datetime):
17
+ """Build base peak."""
18
+ if not start_date.tzinfo:
19
+ self._start_date = start_date.astimezone(EST_TIMEZONE)
20
+ else:
21
+ self._start_date = start_date
22
+ if not end_date.tzinfo:
23
+ self._end_date = end_date.astimezone(EST_TIMEZONE)
24
+ else:
25
+ self._end_date = end_date
26
+
27
+ super().__init__(self.start_date, self.end_date, is_critical=False)
28
+
29
+ @property
30
+ def is_morning(self) -> bool:
31
+ """Return True if it's a morning peak."""
32
+ return self._start_date.hour < 12
33
+
34
+ @property
35
+ def is_evening(self) -> bool:
36
+ """Return True if it's a evening peak."""
37
+ return self._start_date.hour > 12
38
+
39
+ @property
40
+ def day(self) -> datetime.date:
41
+ """Get the day, without time, of the peak."""
42
+ return self._start_date.date()
43
+
44
+ @property
45
+ def date(self) -> datetime.date:
46
+ """DEPRECATED - Get the day, without time, of the peak."""
47
+ return self.day
48
+
49
+ @property
50
+ def start_date(self) -> datetime.datetime:
51
+ """Get the start date of the peak."""
52
+ return self._start_date
53
+
54
+ @property
55
+ def end_date(self) -> datetime.datetime:
56
+ """Get the end date of the peak."""
57
+ return self._end_date
58
+
59
+ @property
60
+ def morning_evening(self) -> str:
61
+ """Return evening or morning peak value."""
62
+ if self.is_morning:
63
+ return "morning"
64
+ if self.is_evening:
65
+ return "evening"
66
+ msg = "Can not determine if is morning or evening peak"
67
+ raise HydroQcCPCPeakError(msg)