Skip to content

FIx compatibility with new app release #17

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "saic_ismart_client_ng"
version = "0.8.1"
version = "0.8.2"
description = "SAIC next gen client library (MG iSMART)"
authors = [
{ name = "Giovanni Condello", email = "saic-python-client@nanomad.net" },
Expand Down
144 changes: 73 additions & 71 deletions src/saic_ismart_client_ng/api/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,21 +33,23 @@
from saic_ismart_client_ng.listener import SaicApiListener
from saic_ismart_client_ng.model import SaicApiConfiguration


class IsDataclass(Protocol):
# as already noted in comments, checking for this attribute is currently
# the most reliable way to ascertain that something is a dataclass
__dataclass_fields__: ClassVar[dict[str, Any]]


T = TypeVar("T", bound=IsDataclass)

logger = logging.getLogger(__name__)


class AbstractSaicApi:
def __init__(
self,
configuration: SaicApiConfiguration,
listener: SaicApiListener | None = None,
self,
configuration: SaicApiConfiguration,
listener: SaicApiListener | None = None,
) -> None:
self.__configuration = configuration
self.__api_client = SaicApiClient(configuration, listener=listener)
Expand All @@ -59,16 +61,16 @@ async def login(self) -> LoginResp:
"Accept": "application/json",
"Authorization": "Basic c3dvcmQ6c3dvcmRfc2VjcmV0",
}
firebase_device_id = "cqSHOMG1SmK4k-fzAeK6hr:APA91bGtGihOG5SEQ9hPx3Dtr9o9mQguNiKZrQzboa-1C_UBlRZYdFcMmdfLvh9Q_xA8A0dGFIjkMhZbdIXOYnKfHCeWafAfLXOrxBS3N18T4Slr-x9qpV6FHLMhE9s7I6s89k9lU7DD"
firebase_device_id = "simulator*********************************************" + str(int(datetime.datetime.now().timestamp()))
form_body = {
"grant_type": "password",
"username": self.__configuration.username,
"password": sha1_hex_digest(self.__configuration.password),
"scope": "all",
"deviceId": f"{firebase_device_id}###europecar",
"deviceType": "1", # 2 for huawei
"deviceId": f"{firebase_device_id}###com.saicmotor.europecar",
"deviceType": "0", # 2 for huawei
"loginType": "2" if self.__configuration.username_is_email else "1",
"countryCode": ""
"language": "EN"
if self.__configuration.username_is_email
else self.__configuration.phone_country_code,
}
Expand All @@ -82,7 +84,7 @@ async def login(self) -> LoginResp:
)
# Update the user token
if not (access_token := result.access_token) or not (
expiration := result.expires_in
expiration := result.expires_in
):
raise SaicApiException(
"Failed to get an access token, please check your credentials"
Expand All @@ -95,16 +97,16 @@ async def login(self) -> LoginResp:
return result

async def execute_api_call(
self,
method: str,
path: str,
*,
body: Any | None = None,
form_body: Any | None = None,
out_type: type[T],
params: QueryParamTypes | None = None,
headers: HeaderTypes | None = None,
allow_null_body: bool = False,
self,
method: str,
path: str,
*,
body: Any | None = None,
form_body: Any | None = None,
out_type: type[T],
params: QueryParamTypes | None = None,
headers: HeaderTypes | None = None,
allow_null_body: bool = False,
) -> T:
result = await self.__execute_api_call(
method,
Expand All @@ -122,15 +124,15 @@ async def execute_api_call(
return result

async def execute_api_call_no_result(
self,
method: str,
path: str,
*,
body: Any | None = None,
form_body: Any | None = None,
params: QueryParamTypes | None = None,
headers: HeaderTypes | None = None,
allow_null_body: bool = False,
self,
method: str,
path: str,
*,
body: Any | None = None,
form_body: Any | None = None,
params: QueryParamTypes | None = None,
headers: HeaderTypes | None = None,
allow_null_body: bool = False,
) -> None:
await self.__execute_api_call(
method,
Expand All @@ -143,16 +145,16 @@ async def execute_api_call_no_result(
)

async def __execute_api_call(
self,
method: str,
path: str,
*,
body: Any | None = None,
form_body: Any | None = None,
out_type: type[T] | None = None,
params: QueryParamTypes | None = None,
headers: HeaderTypes | None = None,
allow_null_body: bool = False,
self,
method: str,
path: str,
*,
body: Any | None = None,
form_body: Any | None = None,
out_type: type[T] | None = None,
params: QueryParamTypes | None = None,
headers: HeaderTypes | None = None,
allow_null_body: bool = False,
) -> T | None:
try:
url = f"{self.__configuration.base_uri}{path.removeprefix('/')}"
Expand All @@ -174,15 +176,15 @@ async def __execute_api_call(
raise SaicApiException(msg, return_code=500) from e

async def execute_api_call_with_event_id(
self,
method: str,
path: str,
*,
body: Any | None = None,
out_type: type[T],
params: QueryParamTypes | None = None,
headers: MutableMapping[str, str] | None = None,
delay: tenacity.wait.WaitBaseT | None = None,
self,
method: str,
path: str,
*,
body: Any | None = None,
out_type: type[T],
params: QueryParamTypes | None = None,
headers: MutableMapping[str, str] | None = None,
delay: tenacity.wait.WaitBaseT | None = None,
) -> T:
result = await self.__execute_api_call_with_event_id(
method,
Expand All @@ -199,14 +201,14 @@ async def execute_api_call_with_event_id(
return result

async def execute_api_call_with_event_id_no_result(
self,
method: str,
path: str,
*,
body: Any | None = None,
params: QueryParamTypes | None = None,
headers: MutableMapping[str, str] | None = None,
delay: tenacity.wait.WaitBaseT | None = None,
self,
method: str,
path: str,
*,
body: Any | None = None,
params: QueryParamTypes | None = None,
headers: MutableMapping[str, str] | None = None,
delay: tenacity.wait.WaitBaseT | None = None,
) -> None:
await self.__execute_api_call_with_event_id(
method,
Expand All @@ -218,15 +220,15 @@ async def execute_api_call_with_event_id_no_result(
)

async def __execute_api_call_with_event_id(
self,
method: str,
path: str,
*,
body: Any | None = None,
out_type: type[T] | None = None,
params: QueryParamTypes | None = None,
headers: MutableMapping[str, str] | None = None,
delay: tenacity.wait.WaitBaseT | None = None,
self,
method: str,
path: str,
*,
body: Any | None = None,
out_type: type[T] | None = None,
params: QueryParamTypes | None = None,
headers: MutableMapping[str, str] | None = None,
delay: tenacity.wait.WaitBaseT | None = None,
) -> T | None:
@tenacity.retry(
stop=tenacity.stop_after_delay(30),
Expand All @@ -251,11 +253,11 @@ async def execute_api_call_with_event_id_inner(*, event_id: str) -> T | None:

# pylint: disable=too-many-branches
async def __deserialize(
self,
request: httpx.Request,
response: httpx.Response,
data_class: type[T] | None,
allow_null_body: bool,
self,
request: httpx.Request,
response: httpx.Response,
data_class: type[T] | None,
allow_null_body: bool,
) -> T | None:
try:
request_event_id = request.headers.get("event-id")
Expand Down Expand Up @@ -348,9 +350,9 @@ def logout(self) -> None:
@property
def is_logged_in(self) -> bool:
return (
self.__api_client.user_token is not None
and self.__token_expiration is not None
and self.__token_expiration > datetime.datetime.now()
self.__api_client.user_token is not None
and self.__token_expiration is not None
and self.__token_expiration > datetime.datetime.now()
)

@property
Expand Down
2 changes: 1 addition & 1 deletion src/saic_ismart_client_ng/net/crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ def encrypt_request(
request_content, key_hex, iv_hex
).encode("utf-8")

original_request_headers["User-Agent"] = "okhttp/3.14.9"
original_request_headers["User-Agent"] = "Europe/2.1.0 (iPad; iOS 18.5; Scale/2.00)"
original_request_headers["Content-Type"] = f"{modified_content_type};charset=utf-8"
original_request_headers["Accept"] = "application/json"
original_request_headers["Accept-Encoding"] = "gzip"
Expand Down