renault-api 0.6.0__tar.gz → 0.6.2__tar.gz
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.
- {renault_api-0.6.0 → renault_api-0.6.2}/PKG-INFO +1 -1
- {renault_api-0.6.0 → renault_api-0.6.2}/pyproject.toml +9 -9
- {renault_api-0.6.0 → renault_api-0.6.2}/pyproject.toml.orig +9 -9
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/cli/renault_client.py +9 -2
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/gigya/enums.py +1 -0
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/gigya/exceptions.py +16 -1
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/gigya/models.py +14 -3
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/kamereon/exceptions.py +6 -0
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/kamereon/models.py +51 -3
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/models.py +1 -1
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/renault_session.py +7 -2
- {renault_api-0.6.0 → renault_api-0.6.2}/LICENSE.rst +0 -0
- {renault_api-0.6.0 → renault_api-0.6.2}/README.rst +0 -0
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/__init__.py +0 -0
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/cli/__init__.py +0 -0
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/cli/__main__.py +0 -0
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/cli/charge/__init__.py +0 -0
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/cli/charge/commands.py +0 -0
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/cli/charge/control.py +0 -0
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/cli/charge/history.py +0 -0
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/cli/charge/schedule.py +0 -0
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/cli/charge/soc_levels.py +0 -0
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/cli/helpers.py +0 -0
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/cli/hvac/__init__.py +0 -0
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/cli/hvac/commands.py +0 -0
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/cli/hvac/control.py +0 -0
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/cli/hvac/history.py +0 -0
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/cli/renault_account.py +0 -0
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/cli/renault_settings.py +0 -0
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/cli/renault_vehicle.py +0 -0
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/const.py +0 -0
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/credential.py +0 -0
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/credential_store.py +0 -0
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/exceptions.py +0 -0
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/gigya/__init__.py +0 -0
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/gigya/schemas.py +0 -0
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/helpers.py +0 -0
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/kamereon/__init__.py +0 -0
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/kamereon/enums.py +0 -0
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/kamereon/helpers.py +0 -0
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/kamereon/schemas.py +0 -0
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/py.typed +0 -0
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/renault_account.py +0 -0
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/renault_client.py +0 -0
- {renault_api-0.6.0 → renault_api-0.6.2}/src/renault_api/renault_vehicle.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "renault-api"
|
|
3
|
-
version = "0.6.
|
|
3
|
+
version = "0.6.2"
|
|
4
4
|
description = "Renault API"
|
|
5
5
|
license = "MIT"
|
|
6
6
|
license-files = ["LICENSE.rst"]
|
|
@@ -40,25 +40,25 @@ Changelog = "https://github.com/hacf-fr/renault-api/releases"
|
|
|
40
40
|
|
|
41
41
|
[dependency-groups]
|
|
42
42
|
dev = [
|
|
43
|
-
"aiointercept==0.
|
|
43
|
+
"aiointercept==1.0.0",
|
|
44
44
|
"codespell==2.4.3",
|
|
45
45
|
"covdefaults==2.3.0",
|
|
46
|
-
"coverage[toml]==7.
|
|
46
|
+
"coverage[toml]==7.17.0",
|
|
47
47
|
"pre-commit-hooks==6.0.0",
|
|
48
|
-
"prek==0.5.
|
|
48
|
+
"prek==0.5.7",
|
|
49
49
|
"pytest==9.1.1",
|
|
50
|
-
"pytest-asyncio==1.4.
|
|
50
|
+
"pytest-asyncio==1.4.1",
|
|
51
51
|
"pytest-cov==7.1.0",
|
|
52
|
-
"ruff==0.17.
|
|
52
|
+
"ruff==0.17.2",
|
|
53
53
|
"syrupy==6.1.1",
|
|
54
|
-
"ty==0.0.
|
|
54
|
+
"ty==0.0.92",
|
|
55
55
|
"yamllint==1.38.0",
|
|
56
|
-
"zizmor==1.
|
|
56
|
+
"zizmor==1.31.1",
|
|
57
57
|
]
|
|
58
58
|
docs = [
|
|
59
59
|
"furo==2025.12.19; python_version >= '3.14'",
|
|
60
60
|
"myst-parser==5.1.0; python_version >= '3.14'",
|
|
61
|
-
"sphinx==9.
|
|
61
|
+
"sphinx==9.2.0; python_version >= '3.14'",
|
|
62
62
|
"sphinx-autobuild==2025.8.25; python_version >= '3.14'",
|
|
63
63
|
"sphinx-click==6.2.0; python_version >= '3.14'",
|
|
64
64
|
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "renault-api"
|
|
3
|
-
version = "0.6.
|
|
3
|
+
version = "0.6.2"
|
|
4
4
|
description = "Renault API"
|
|
5
5
|
authors = [{ name = "epenet" }]
|
|
6
6
|
license = "MIT"
|
|
@@ -40,26 +40,26 @@ Changelog = "https://github.com/hacf-fr/renault-api/releases"
|
|
|
40
40
|
|
|
41
41
|
[dependency-groups]
|
|
42
42
|
dev = [
|
|
43
|
-
"aiointercept==0.
|
|
43
|
+
"aiointercept==1.0.0",
|
|
44
44
|
"codespell==2.4.3",
|
|
45
45
|
"covdefaults==2.3.0",
|
|
46
|
-
"coverage[toml]==7.
|
|
46
|
+
"coverage[toml]==7.17.0",
|
|
47
47
|
"pre-commit-hooks==6.0.0",
|
|
48
|
-
"prek==0.5.
|
|
48
|
+
"prek==0.5.7",
|
|
49
49
|
"pytest==9.1.1",
|
|
50
|
-
"pytest-asyncio==1.4.
|
|
50
|
+
"pytest-asyncio==1.4.1",
|
|
51
51
|
"pytest-cov==7.1.0",
|
|
52
|
-
"ruff==0.17.
|
|
52
|
+
"ruff==0.17.2",
|
|
53
53
|
"syrupy==6.1.1",
|
|
54
|
-
"ty==0.0.
|
|
54
|
+
"ty==0.0.92",
|
|
55
55
|
"yamllint==1.38.0",
|
|
56
|
-
"zizmor==1.
|
|
56
|
+
"zizmor==1.31.1",
|
|
57
57
|
]
|
|
58
58
|
# docs (Python >= 3.14)
|
|
59
59
|
docs = [
|
|
60
60
|
"furo==2025.12.19; python_version >= '3.14'",
|
|
61
61
|
"myst-parser==5.1.0; python_version >= '3.14'",
|
|
62
|
-
"sphinx==9.
|
|
62
|
+
"sphinx==9.2.0; python_version >= '3.14'",
|
|
63
63
|
"sphinx-autobuild==2025.8.25; python_version >= '3.14'",
|
|
64
64
|
"sphinx-click==6.2.0; python_version >= '3.14'",
|
|
65
65
|
]
|
|
@@ -17,6 +17,7 @@ from renault_api.credential import Credential
|
|
|
17
17
|
from renault_api.credential_store import CredentialStore
|
|
18
18
|
from renault_api.exceptions import RenaultException
|
|
19
19
|
from renault_api.gigya import GIGYA_LOGIN_TOKEN
|
|
20
|
+
from renault_api.gigya.exceptions import InvalidVerificationCodeException
|
|
20
21
|
from renault_api.gigya.exceptions import PendingTwoFactorAuthenticationException
|
|
21
22
|
from renault_api.helpers import get_api_keys
|
|
22
23
|
from renault_api.renault_client import RenaultClient
|
|
@@ -133,8 +134,14 @@ async def _prompt_two_factor_auth(session: RenaultSession) -> None:
|
|
|
133
134
|
)
|
|
134
135
|
email_id = emails[choice - 1].id
|
|
135
136
|
await session.request_two_factor_auth_code(email_id)
|
|
136
|
-
|
|
137
|
-
|
|
137
|
+
while True:
|
|
138
|
+
code = click.prompt("Verification code")
|
|
139
|
+
try:
|
|
140
|
+
await session.complete_two_factor_auth(code)
|
|
141
|
+
except InvalidVerificationCodeException:
|
|
142
|
+
click.echo("Wrong verification code, please try again.", err=True)
|
|
143
|
+
else:
|
|
144
|
+
return
|
|
138
145
|
|
|
139
146
|
|
|
140
147
|
async def login(
|
|
@@ -14,6 +14,7 @@ class GigyaResponseException(GigyaException):
|
|
|
14
14
|
|
|
15
15
|
def __init__(self, error_code: int, error_details: str | None):
|
|
16
16
|
"""Initialise GigyaResponseException."""
|
|
17
|
+
super().__init__(error_code, error_details)
|
|
17
18
|
self.error_code = error_code
|
|
18
19
|
self.error_details = error_details
|
|
19
20
|
|
|
@@ -24,6 +25,16 @@ class InvalidCredentialsException(GigyaResponseException):
|
|
|
24
25
|
pass
|
|
25
26
|
|
|
26
27
|
|
|
28
|
+
class InvalidVerificationCodeException(GigyaResponseException):
|
|
29
|
+
"""Wrong two-factor authentication code (Gigya errorCode 400006).
|
|
30
|
+
|
|
31
|
+
Only raised when completing the email verification: the same challenge
|
|
32
|
+
can be retried with the right code.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
|
|
27
38
|
class PendingTwoFactorAuthenticationException(GigyaResponseException):
|
|
28
39
|
"""Account requires two-factor authentication (Gigya errorCode 403101).
|
|
29
40
|
|
|
@@ -33,6 +44,10 @@ class PendingTwoFactorAuthenticationException(GigyaResponseException):
|
|
|
33
44
|
"""
|
|
34
45
|
|
|
35
46
|
def __init__(self, error_code: int, error_details: str | None, reg_token: str):
|
|
36
|
-
"""Initialise PendingTwoFactorAuthenticationException.
|
|
47
|
+
"""Initialise PendingTwoFactorAuthenticationException.
|
|
48
|
+
|
|
49
|
+
`reg_token` is kept out of `args`, so that it doesn't appear in
|
|
50
|
+
`str()`/`repr()` of the exception, or in logged tracebacks.
|
|
51
|
+
"""
|
|
37
52
|
super().__init__(error_code, error_details)
|
|
38
53
|
self.reg_token = reg_token
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
from typing import Any
|
|
5
|
+
from typing import ClassVar
|
|
5
6
|
|
|
6
7
|
from . import exceptions
|
|
7
8
|
from .enums import GigyaErrorCode
|
|
@@ -19,6 +20,9 @@ COMMON_ERRRORS: list[dict[str, Any]] = [
|
|
|
19
20
|
class GigyaResponse(BaseModel):
|
|
20
21
|
"""Gigya response."""
|
|
21
22
|
|
|
23
|
+
# Errors specific to an endpoint, checked before COMMON_ERRRORS
|
|
24
|
+
_SPECIFIC_ERRORS: ClassVar[list[dict[str, Any]]] = []
|
|
25
|
+
|
|
22
26
|
errorCode: int
|
|
23
27
|
errorDetails: str | None
|
|
24
28
|
regToken: str | None
|
|
@@ -33,9 +37,9 @@ class GigyaResponse(BaseModel):
|
|
|
33
37
|
raise exceptions.PendingTwoFactorAuthenticationException(
|
|
34
38
|
self.errorCode, self.errorDetails, self.regToken
|
|
35
39
|
)
|
|
36
|
-
for
|
|
37
|
-
if self.errorCode ==
|
|
38
|
-
error_type =
|
|
40
|
+
for known_error in (*self._SPECIFIC_ERRORS, *COMMON_ERRRORS):
|
|
41
|
+
if self.errorCode == known_error["errorCode"]:
|
|
42
|
+
error_type = known_error["error_type"]
|
|
39
43
|
raise error_type(self.errorCode, self.errorDetails)
|
|
40
44
|
raise exceptions.GigyaResponseException(self.errorCode, self.errorDetails)
|
|
41
45
|
|
|
@@ -160,6 +164,13 @@ class GigyaTfaSendEmailCodeResponse(GigyaResponse):
|
|
|
160
164
|
class GigyaTfaEmailCompleteVerificationResponse(GigyaResponse):
|
|
161
165
|
"""Gigya response to GET on /accounts.tfa.email.completeVerification."""
|
|
162
166
|
|
|
167
|
+
_SPECIFIC_ERRORS: ClassVar[list[dict[str, Any]]] = [
|
|
168
|
+
{
|
|
169
|
+
"errorCode": GigyaErrorCode.INVALID_PARAMETER_VALUE,
|
|
170
|
+
"error_type": exceptions.InvalidVerificationCodeException,
|
|
171
|
+
}
|
|
172
|
+
]
|
|
173
|
+
|
|
163
174
|
providerAssertion: str | None
|
|
164
175
|
|
|
165
176
|
def get_provider_assertion(self) -> str:
|
|
@@ -63,6 +63,10 @@ COMMON_ERRRORS: list[dict[str, Any]] = [
|
|
|
63
63
|
"errorCode": "409001",
|
|
64
64
|
"error_type": exceptions.ChargeModeInProgressException,
|
|
65
65
|
},
|
|
66
|
+
{
|
|
67
|
+
"errorCode": "err.func.wired.low.battery.level",
|
|
68
|
+
"error_type": exceptions.InsufficientBatteryLevelException,
|
|
69
|
+
},
|
|
66
70
|
]
|
|
67
71
|
|
|
68
72
|
VEHICLE_SPECIFICATIONS: dict[str, dict[str, Any]] = {
|
|
@@ -390,6 +394,34 @@ _VEHICLE_ENDPOINTS: dict[str, dict[str, EndpointDefinition | None]] = {
|
|
|
390
394
|
"res-state": None, # Reason: "err.func.wired.notFound"
|
|
391
395
|
"soc-levels": None, # Reason: "err.func.wired.forbidden"
|
|
392
396
|
},
|
|
397
|
+
"XBG2VE": { # DACIA SPRING MY25
|
|
398
|
+
"actions/charge-start": _KCM_ENDPOINTS["actions/charge-start-via-settings"],
|
|
399
|
+
"actions/charge-stop": None, # Reason: "err.func.wired.forbidden" (kcm pause-resume) / "err.func.wired.invalid-body-format" (default) # noqa: E501
|
|
400
|
+
"actions/horn-start": _DEFAULT_ENDPOINTS["actions/horn-start"],
|
|
401
|
+
"actions/hvac-start": _DEFAULT_ENDPOINTS["actions/hvac-start"],
|
|
402
|
+
# hvac-stop: "cancel" is accepted (200) but has no effect, "stop" works
|
|
403
|
+
"actions/hvac-stop": _KCA_ALTERNATIVE_ENDPOINTS["actions/hvac-stop"],
|
|
404
|
+
"actions/lights-start": _DEFAULT_ENDPOINTS["actions/lights-start"],
|
|
405
|
+
"actions/refresh-location": _DEFAULT_ENDPOINTS["actions/refresh-location"],
|
|
406
|
+
"alerts": None, # Reason: "err.func.wired.not-found"
|
|
407
|
+
"battery-status": _DEFAULT_ENDPOINTS["battery-status"],
|
|
408
|
+
"charge-history": None, # Reason: "err.func.wired.not-found"
|
|
409
|
+
"charge-mode": None, # Reason: "err.func.wired.forbidden"
|
|
410
|
+
"charge-schedule": _KCM_ENDPOINTS["charge-schedule-via-settings"],
|
|
411
|
+
"charges": _DEFAULT_ENDPOINTS["charges"],
|
|
412
|
+
"charging-settings": None, # Reason: "err.func.wired.forbidden"
|
|
413
|
+
"cockpit": _DEFAULT_ENDPOINTS["cockpit"],
|
|
414
|
+
"hvac-history": None, # Reason: "err.func.wired.not-found"
|
|
415
|
+
"hvac-sessions": None, # Reason: "err.func.wired.not-found"
|
|
416
|
+
"hvac-settings": _DEFAULT_ENDPOINTS["hvac-settings"],
|
|
417
|
+
"hvac-status": _DEFAULT_ENDPOINTS["hvac-status"],
|
|
418
|
+
"location": _DEFAULT_ENDPOINTS["location"],
|
|
419
|
+
"lock-status": None, # Reason: "There is no data for this vin and uid" (404)
|
|
420
|
+
"notification-settings": None, # Reason: "The vehicle does not have a GDC gateway" (400001) # noqa: E501
|
|
421
|
+
"pressure": None, # Reason: "There is no data for this vin and uid" (404)
|
|
422
|
+
"res-state": None, # Reason: "There is no data for this vin and uid" (404)
|
|
423
|
+
"soc-levels": _DEFAULT_ENDPOINTS["soc-levels"],
|
|
424
|
+
},
|
|
393
425
|
"XCB1SE": { # SCENIC E-TECH
|
|
394
426
|
"actions/charge-start": _KCM_ENDPOINTS["actions/charge-start-via-settings"],
|
|
395
427
|
"actions/charge-stop": None, # Reason: "err.func.wired.forbidden" (kcm pause-resume) / "err.func.wired.invalid-body-format" (default) # noqa: E501
|
|
@@ -552,11 +584,11 @@ _VEHICLE_ENDPOINTS: dict[str, dict[str, EndpointDefinition | None]] = {
|
|
|
552
584
|
"charge-mode": None, # Reason: "err.func.wired.forbidden"
|
|
553
585
|
"charging-settings": None, # Reason: "err.func.wired.forbidden"
|
|
554
586
|
"cockpit": _DEFAULT_ENDPOINTS["cockpit"], # confirmed
|
|
555
|
-
"hvac-status": None,
|
|
587
|
+
"hvac-status": None, # Reason: "err.func.wired.notFound"
|
|
556
588
|
"location": _DEFAULT_ENDPOINTS["location"],
|
|
557
|
-
"lock-status":
|
|
589
|
+
"lock-status": _DEFAULT_ENDPOINTS["lock-status"], # confirmed
|
|
558
590
|
"pressure": None, # Reason: 404
|
|
559
|
-
"res-state": None,
|
|
591
|
+
"res-state": None, # Reason: "err.func.wired.notFound"
|
|
560
592
|
"soc-levels": None, # Reason: "err.func.wired.forbidden"
|
|
561
593
|
},
|
|
562
594
|
"XJA1VP": { # CLIO V
|
|
@@ -1160,6 +1192,16 @@ class KamereonVehicleCockpitData(KamereonVehicleDataAttributes):
|
|
|
1160
1192
|
totalMileage: float | None
|
|
1161
1193
|
|
|
1162
1194
|
|
|
1195
|
+
# Some vehicles (eg. XJB2SU, XHN1SU) report the door statuses as statusDoor*
|
|
1196
|
+
_LOCK_STATUS_ALIASES = {
|
|
1197
|
+
"doorStatusRearLeft": "statusDoorRearLeft",
|
|
1198
|
+
"doorStatusRearRight": "statusDoorRearRight",
|
|
1199
|
+
"doorStatusDriver": "statusDoorDriver",
|
|
1200
|
+
"doorStatusPassenger": "statusDoorPassenger",
|
|
1201
|
+
"hatchStatus": "statusDoorTailGate",
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
|
|
1163
1205
|
@dataclass
|
|
1164
1206
|
class KamereonVehicleLockStatusData(KamereonVehicleDataAttributes):
|
|
1165
1207
|
"""Kamereon vehicle data lock-status attributes."""
|
|
@@ -1172,6 +1214,12 @@ class KamereonVehicleLockStatusData(KamereonVehicleDataAttributes):
|
|
|
1172
1214
|
hatchStatus: str | None
|
|
1173
1215
|
lastUpdateTime: str | None
|
|
1174
1216
|
|
|
1217
|
+
def __post_init__(self) -> None:
|
|
1218
|
+
"""Fall back to the statusDoor* attributes used by newer vehicles."""
|
|
1219
|
+
for field, alias in _LOCK_STATUS_ALIASES.items():
|
|
1220
|
+
if getattr(self, field) is None:
|
|
1221
|
+
setattr(self, field, self.raw_data.get(alias))
|
|
1222
|
+
|
|
1175
1223
|
|
|
1176
1224
|
@dataclass
|
|
1177
1225
|
class KamereonVehicleResStateData(KamereonVehicleDataAttributes):
|
|
@@ -16,7 +16,7 @@ class BaseModel:
|
|
|
16
16
|
class BaseSchema(marshmallow.Schema):
|
|
17
17
|
"""Base schema for Gigya and Kamereon models to exclude unknown fields."""
|
|
18
18
|
|
|
19
|
-
class Meta:
|
|
19
|
+
class Meta: # ty: ignore[invalid-attribute-override] # astral-sh/ty#4711
|
|
20
20
|
"""Force unknown fields to 'exclude'."""
|
|
21
21
|
|
|
22
22
|
unknown = marshmallow.EXCLUDE
|
|
@@ -157,10 +157,15 @@ class RenaultSession:
|
|
|
157
157
|
) -> None:
|
|
158
158
|
"""Submit the emailed one-time code and finish logging in.
|
|
159
159
|
|
|
160
|
+
Raises `InvalidVerificationCodeException` if the code is wrong. The
|
|
161
|
+
challenge stays pending, so this can be called again with the right
|
|
162
|
+
code, without requesting a new one.
|
|
163
|
+
|
|
160
164
|
When `remember_device` is True (the default), Gigya should not ask
|
|
161
165
|
for another code for this device for about 30 days. The device is
|
|
162
|
-
identified by
|
|
163
|
-
|
|
166
|
+
identified by `device_ids`, which are sent explicitly with each
|
|
167
|
+
request (so the aiohttp session may be shared): persist them with
|
|
168
|
+
`device_ids`, and restore them with `set_device_ids`.
|
|
164
169
|
"""
|
|
165
170
|
pending_tfa = self._get_pending_tfa()
|
|
166
171
|
if not pending_tfa.gigya_assertion or not pending_tfa.phv_token:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|