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.
- {hydro_quebec_api_wrapper-4.2.6.dist-info → hydro_quebec_api_wrapper-4.3.0.dist-info}/METADATA +58 -6
- {hydro_quebec_api_wrapper-4.2.6.dist-info → hydro_quebec_api_wrapper-4.3.0.dist-info}/RECORD +11 -10
- hydroqc/consts.py +20 -0
- hydroqc/error.py +21 -0
- hydroqc/hydro_api/client.py +599 -130
- hydroqc/hydro_api/mfa.py +762 -0
- hydroqc/webuser.py +65 -2
- {hydro_quebec_api_wrapper-4.2.6.dist-info → hydro_quebec_api_wrapper-4.3.0.dist-info}/WHEEL +0 -0
- {hydro_quebec_api_wrapper-4.2.6.dist-info → hydro_quebec_api_wrapper-4.3.0.dist-info}/entry_points.txt +0 -0
- {hydro_quebec_api_wrapper-4.2.6.dist-info → hydro_quebec_api_wrapper-4.3.0.dist-info}/licenses/LICENSE +0 -0
- {hydro_quebec_api_wrapper-4.2.6.dist-info → hydro_quebec_api_wrapper-4.3.0.dist-info}/top_level.txt +0 -0
{hydro_quebec_api_wrapper-4.2.6.dist-info → hydro_quebec_api_wrapper-4.3.0.dist-info}/METADATA
RENAMED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Hydro-Quebec-API-Wrapper
|
|
3
|
-
Version: 4.
|
|
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://
|
|
9
|
-
Project-URL: Bug Reports, https://
|
|
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
|
-
[](https://github.com/hydroqc/hydroqc/actions/workflows/ci.yml)
|
|
29
|
+
[](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://
|
|
159
|
+
git clone https://github.com/hydroqc/hydroqc.git
|
|
108
160
|
```
|
|
109
161
|
|
|
110
162
|
2. Create a virtual-env
|
{hydro_quebec_api_wrapper-4.2.6.dist-info → hydro_quebec_api_wrapper-4.3.0.dist-info}/RECORD
RENAMED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
hydro_quebec_api_wrapper-4.
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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.
|
|
50
|
-
hydro_quebec_api_wrapper-4.
|
|
51
|
-
hydro_quebec_api_wrapper-4.
|
|
52
|
-
hydro_quebec_api_wrapper-4.
|
|
53
|
-
hydro_quebec_api_wrapper-4.
|
|
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
|
|