Hydro-Quebec-API-Wrapper 4.2.6__py3-none-any.whl → 4.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.
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Hydro-Quebec-API-Wrapper
3
- Version: 4.2.6
3
+ Version: 4.3.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>
7
7
  License: LGPL-3.0-or-later
8
- Project-URL: Source Code, https://gitlab.com/hydroqc/hydroqc
9
- Project-URL: Bug Reports, https://gitlab.com/hydroqc/hydroqc/-/issues
8
+ Project-URL: Source Code, https://github.com/hydroqc/hydroqc
9
+ Project-URL: Bug Reports, https://github.com/hydroqc/hydroqc/issues
10
10
  Project-URL: Home-page, https://hydroqc.ca
11
11
  Keywords: hydroquebec,lib
12
12
  Platform: any
@@ -25,8 +25,8 @@ Dynamic: license-file
25
25
  # HydroQC - Hydro Quebec API wrapper
26
26
 
27
27
 
28
- [![coverage report](https://gitlab.com/hydroqc/hydroqc/badges/main/coverage.svg)](https://gitlab.com/hydroqc/hydroqc/-/commits/main)
29
- [![pipeline status](https://gitlab.com/hydroqc/hydroqc/badges/main/pipeline.svg)](https://gitlab.com/hydroqc/hydroqc/-/commits/main)
28
+ [![CI](https://github.com/hydroqc/hydroqc/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/hydroqc/hydroqc/actions/workflows/ci.yml)
29
+ [![PyPI](https://img.shields.io/pypi/v/Hydro-Quebec-API-Wrapper.svg)](https://pypi.org/project/Hydro-Quebec-API-Wrapper/)
30
30
 
31
31
 
32
32
 
@@ -89,6 +89,58 @@ We don't have anyone to test with. Please reach out to us if you have this plan
89
89
 
90
90
  If you need more information about the usage and current integrations of the library please see our website [https://hydroqc.ca](https://hydroqc.ca)
91
91
 
92
+ ## E-mail verification (MFA)
93
+
94
+ Hydro-Québec can ask for a one-time code sent by e-mail after the password round
95
+ trip. `login()` returns a `LoginStatus` instead of a plain boolean:
96
+
97
+ ```python
98
+ from hydroqc.consts import LoginStatus
99
+ from hydroqc.error import HydroQcMFAInvalidCodeError
100
+
101
+ status = await webuser.login()
102
+ while status is LoginStatus.MFA_REQUIRED:
103
+ code = input("Verification code (empty = send a new one): ")
104
+ if not code.strip():
105
+ await webuser.request_mfa_code()
106
+ continue
107
+ try:
108
+ status = await webuser.submit_mfa_code(code)
109
+ except HydroQcMFAInvalidCodeError:
110
+ print("Code refused, try again.")
111
+
112
+ if status is not LoginStatus.SUCCESS:
113
+ raise RuntimeError("Login failed")
114
+ ```
115
+
116
+ `bool(status)` is only true for `LoginStatus.SUCCESS`, so existing code using
117
+ `if await webuser.login():` keeps working. The code is requested automatically
118
+ when the step is detected; `request_mfa_code()` sends a new one. The
119
+ verification step expires after a few minutes (3 minutes on the current
120
+ portal): `HydroQcMFAExpiredError` means the login has to be restarted.
121
+
122
+ A silent background login cannot ask for a code and raises
123
+ `HydroQcMFARequiredError` instead of failing quietly.
124
+
125
+ ### Reusing a session
126
+
127
+ `export_session_state()` returns the OAuth tokens plus the Azure AD B2C cookies;
128
+ `import_session_state()` restores them. Persisting that state lets a later run
129
+ skip the password (and a new verification code) while the refresh token or the
130
+ single sign-on cookie is still valid. The current portal issues 15-minute access
131
+ tokens and a 24-hour refresh token, so the stored session is normally reusable
132
+ for about a day; once the refresh token and the single sign-on cookie are both
133
+ refused, a new interactive login (and a new code) is required:
134
+
135
+ ```python
136
+ state = webuser.export_session_state() # store it somewhere safe
137
+ # ... next run ...
138
+ webuser = WebUser(username, password, verify_ssl=True)
139
+ webuser.import_session_state(state)
140
+ if await webuser.try_silent_login() is not LoginStatus.SUCCESS:
141
+ await webuser.login()
142
+ ```
143
+
92
144
  ## Goal
93
145
 
94
146
  Make it easy to fetch and manipulate data from Hydro-Quebec, especially the winter credit periods
@@ -104,7 +156,7 @@ This uses python 3.10
104
156
  1. Clone the repo
105
157
 
106
158
  ```bash
107
- git clone https://gitlab.com/hydroqc/hydroqc.git
159
+ git clone https://github.com/hydroqc/hydroqc.git
108
160
  ```
109
161
 
110
162
  2. Create a virtual-env
@@ -1,16 +1,16 @@
1
- hydro_quebec_api_wrapper-4.2.6.dist-info/licenses/LICENSE,sha256=46mU2C5kSwOnkqkw9XQAJlhBL2JAf1_uCD8lVcXyMRg,7652
1
+ hydro_quebec_api_wrapper-4.3.0.dist-info/licenses/LICENSE,sha256=46mU2C5kSwOnkqkw9XQAJlhBL2JAf1_uCD8lVcXyMRg,7652
2
2
  hydroqc/__init__.py,sha256=sFhCh7-LWoISCQuAtdrLQOmj2x5wjHBGbAAPg4SN6Pc,40
3
3
  hydroqc/account.py,sha256=84PO2npB_VG8cEzGIbmXj9sD1whG8qynkiGd-yEYddM,3375
4
- hydroqc/consts.py,sha256=a9dWq5i6t5VedYosUbP8zIuG2q5TPw4ruT0jOaC8Yo8,209
4
+ hydroqc/consts.py,sha256=CcqeInTfcpBe-u1-KGHUHd2SVv6Hq_27I8vLW5W53oM,768
5
5
  hydroqc/customer.py,sha256=Z3zq1p6ZwCXsxMnE8R6RmrGOCTxUFualMuu9MPRwQLI,6267
6
6
  hydroqc/diagnostic.py,sha256=eVY3HfMcLCjTbKWo9xdaw14ygju4qUIKy9pxcQstoRc,17322
7
- hydroqc/error.py,sha256=2HzWBak_RpuhfIrLhnfgNuY3FhUwwNyo1BLUDH6U0CI,598
7
+ hydroqc/error.py,sha256=rrtPTcG_99DLqubrux2gG50FX9f3hSnWpNWja6j9iio,1312
8
8
  hydroqc/logger.py,sha256=Fez6CqjBBxd841U3avtnrT3E43Vyy-v1e7hqwUmf9bg,1256
9
9
  hydroqc/public_client.py,sha256=pUXA9rkgA8b4wIA3EMsd8FAC-swp-IRa4S-VK0v60SQ,3065
10
10
  hydroqc/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  hydroqc/timerange.py,sha256=MXR9yau72m4UZBSmN1qB0jMs0x4_jEZYnwtaUnGq-MQ,1137
12
12
  hydroqc/utils.py,sha256=sxA5qkOA-Ui5jgEYItFQIeQdBsS_5z8J3xn8YxKZMow,232
13
- hydroqc/webuser.py,sha256=MqlZNLLpuLGxHfRKyyVj-2SbDeWq7ERFZAnvvvzqVbo,4578
13
+ hydroqc/webuser.py,sha256=2NiRoGB9yA5xvmHbNQV2DOhtn7o5ueG4cSjQv13ccKU,7206
14
14
  hydroqc/contract/__init__.py,sha256=uqxjOgyhpKFd-n_umxdaW5FB_DkJJaettWnEFC0nRYc,548
15
15
  hydroqc/contract/common.py,sha256=xVw9z_Yjqp95SGCPNAfJVABvgp_C-KM51VTBEU9wjeM,15705
16
16
  hydroqc/contract/contract_d.py,sha256=6jPYTCjFq8zonfbXPrEujDVz58n5RUEuq54VCGqph6o,856
@@ -23,8 +23,9 @@ hydroqc/contract/contract_residential.py,sha256=v88mWvJsJP7FsY5cHmr0ZYYT_S-6xtSk
23
23
  hydroqc/contract/outage.py,sha256=K5XX0enSKMVpL-JCkWsykuKUw-5_GcSJLzcSR1SqQDk,3979
24
24
  hydroqc/hydro_api/__init__.py,sha256=bIIjTkhQcXG7kn4cQr_Q6tFSKGjAx-AXLjMMlRCjclY,32
25
25
  hydroqc/hydro_api/cache.py,sha256=14nLVxV8Q2d9qnEi2iKK9exkhe5WCFueGu9uX5y_tyU,1716
26
- hydroqc/hydro_api/client.py,sha256=yTNBoVriSTjQsdF1b4rPjtN2NlOBnl7i8BRiTKqRv-I,38874
26
+ hydroqc/hydro_api/client.py,sha256=YUQOnMJ3OTRG-j52aT93pkYI9UuWAETrQPaRMQ-DbWs,58946
27
27
  hydroqc/hydro_api/consts.py,sha256=XDNOjZVpC-Z7rsOL4GDgfexb7m8zc7HiafiKGiFvoUI,4607
28
+ hydroqc/hydro_api/mfa.py,sha256=UDfmXHjb4l5ggM1K9noAUQy2kO_yLZBHB-fqnUwtSx0,27825
28
29
  hydroqc/peak/__init__.py,sha256=ye2KbWXhJEHZnBDaFylWioy3W-mzJw_lDplsJNAapNs,19
29
30
  hydroqc/peak/basepeak.py,sha256=DMXjZXyNRHWnuhB0Vp3ZINGhNQngG7AU15cDLajmC7M,1954
30
31
  hydroqc/peak/basepeakhandler.py,sha256=XVemlzDd2IeriDdvunvTxV7qwH1qEJcc0zm5-C716Ts,9184
@@ -46,8 +47,8 @@ hydroqc/types/cpc.py,sha256=ghmtBTT1FKuqVodJJ0kCtUbYNTLCOrngxgcrzjJYvok,2116
46
47
  hydroqc/types/dpc.py,sha256=mwA5T9fGcNTA-kb_f25wq2ALJCwa5lKABOS73L6znY0,1981
47
48
  hydroqc/types/dt.py,sha256=rEXDgNewhlOsRbso0Shlq69TvJFoT56EGYOAjTFZU80,693
48
49
  hydroqc/types/outage.py,sha256=XTsNyu8mmxUx44XWvOOUHKSoijp-lgbXwQroiqm9S0o,3932
49
- hydro_quebec_api_wrapper-4.2.6.dist-info/METADATA,sha256=2oqKBuMOjWQ_GdQlfpZXqvaDRyYPVIEViT6fD-2MAlk,7949
50
- hydro_quebec_api_wrapper-4.2.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
51
- hydro_quebec_api_wrapper-4.2.6.dist-info/entry_points.txt,sha256=G2YKkCH9rFt2dXbFyHINBp4bmqY2HTEIaf7F1CwbC_k,57
52
- hydro_quebec_api_wrapper-4.2.6.dist-info/top_level.txt,sha256=YAcYLnu5YYkbxt1psUoBfo_opl-lAZKq0A7B3IN_P6M,8
53
- hydro_quebec_api_wrapper-4.2.6.dist-info/RECORD,,
50
+ hydro_quebec_api_wrapper-4.3.0.dist-info/METADATA,sha256=-rhBBK2NsxhFiVvO1Ak-bqRaA79rT_cQcHz_jnHdJsI,10017
51
+ hydro_quebec_api_wrapper-4.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
52
+ hydro_quebec_api_wrapper-4.3.0.dist-info/entry_points.txt,sha256=G2YKkCH9rFt2dXbFyHINBp4bmqY2HTEIaf7F1CwbC_k,57
53
+ hydro_quebec_api_wrapper-4.3.0.dist-info/top_level.txt,sha256=YAcYLnu5YYkbxt1psUoBfo_opl-lAZKq0A7B3IN_P6M,8
54
+ hydro_quebec_api_wrapper-4.3.0.dist-info/RECORD,,
hydroqc/consts.py CHANGED
@@ -1,8 +1,28 @@
1
1
  """Hydroqc Consts."""
2
2
 
3
+ from enum import StrEnum
4
+
3
5
  from dateutil import tz
4
6
 
5
7
  # Always get the time using HydroQuebec Local Time
6
8
  HQ_TIMEZONE = tz.gettz("America/Toronto")
7
9
 
8
10
  LOGGING_LEVELS = ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL")
11
+
12
+
13
+ class LoginStatus(StrEnum):
14
+ """Outcome of a login attempt.
15
+
16
+ ``bool(status)`` is only true for :attr:`SUCCESS`, so the historic
17
+ ``if await webuser.login():`` idiom keeps behaving as before while callers
18
+ that need to react to the new e-mail verification step can inspect the
19
+ value.
20
+ """
21
+
22
+ SUCCESS = "success"
23
+ MFA_REQUIRED = "mfa_required"
24
+ FAILED = "failed"
25
+
26
+ def __bool__(self) -> bool:
27
+ """Return True only when the login actually succeeded."""
28
+ return self is LoginStatus.SUCCESS
hydroqc/error.py CHANGED
@@ -15,6 +15,27 @@ class HydroQcHTTPError(HydroQcError):
15
15
  super().__init__(message)
16
16
 
17
17
 
18
+ class HydroQcMFARequiredError(HydroQcError):
19
+ """The login needs a one-time code that cannot be requested here.
20
+
21
+ Raised when a silent re-login (session expired in the background) lands on
22
+ the e-mail verification step: there is nobody to ask for the code at that
23
+ point, the caller has to start an interactive login again.
24
+ """
25
+
26
+
27
+ class HydroQcMFAInvalidCodeError(HydroQcMFARequiredError):
28
+ """The one-time code was refused by Hydro-Québec.
29
+
30
+ The verification step is still valid, the caller can ask the user for
31
+ another code and try again.
32
+ """
33
+
34
+
35
+ class HydroQcMFAExpiredError(HydroQcMFARequiredError):
36
+ """The verification step expired and the login flow has to be restarted."""
37
+
38
+
18
39
  class HydroQcAnnualError(HydroQcError):
19
40
  """Annual HydroQc Error."""
20
41