1
0
mirror of https://github.com/no2chem/wideq.git synced 2025-05-16 07:10:09 -07:00

Add type information

This helps later refactoring.
Use mypy to check types.
This commit is contained in:
Frederik Gladhorn 2020-01-10 14:24:01 +01:00
parent 09696d94f4
commit e98d2a18d1
2 changed files with 42 additions and 39 deletions

View File

@ -7,7 +7,7 @@ import logging
import requests import requests
import base64 import base64
from collections import namedtuple from collections import namedtuple
from typing import Any, Optional from typing import Any, Dict, Generator, List, Optional
from . import core from . import core
@ -25,17 +25,17 @@ class Monitor(object):
makes one `Monitor` object suitable for long-term monitoring. makes one `Monitor` object suitable for long-term monitoring.
""" """
def __init__(self, session, device_id): def __init__(self, session, device_id) -> None:
self.session = session self.session = session
self.device_id = device_id self.device_id = device_id
def start(self): def start(self) -> None:
self.work_id = self.session.monitor_start(self.device_id) self.work_id = self.session.monitor_start(self.device_id)
def stop(self): def stop(self) -> None:
self.session.monitor_stop(self.device_id, self.work_id) self.session.monitor_stop(self.device_id, self.work_id)
def poll(self): def poll(self) -> Optional[bytes]:
"""Get the current status data (a bytestring) or None if the """Get the current status data (a bytestring) or None if the
device is not yet ready. device is not yet ready.
""" """
@ -49,12 +49,12 @@ class Monitor(object):
return None return None
@staticmethod @staticmethod
def decode_json(data): def decode_json(data: bytes) -> Dict[str, Any]:
"""Decode a bytestring that encodes JSON status data.""" """Decode a bytestring that encodes JSON status data."""
return json.loads(data.decode('utf8')) return json.loads(data.decode('utf8'))
def poll_json(self): def poll_json(self) -> Optional[Dict[str, Any]]:
"""For devices where status is reported via JSON data, get the """For devices where status is reported via JSON data, get the
decoded status result (or None if status is not available). decoded status result (or None if status is not available).
""" """
@ -62,11 +62,11 @@ class Monitor(object):
data = self.poll() data = self.poll()
return self.decode_json(data) if data else None return self.decode_json(data) if data else None
def __enter__(self): def __enter__(self) -> 'Monitor':
self.start() self.start()
return self return self
def __exit__(self, type, value, tb): def __exit__(self, type, value, tb) -> None:
self.stop() self.stop()
@ -76,26 +76,27 @@ class Client(object):
""" """
def __init__(self, gateway=None, auth=None, session=None, def __init__(self, gateway=None, auth=None, session=None,
country=DEFAULT_COUNTRY, language=DEFAULT_LANGUAGE): country: str = DEFAULT_COUNTRY,
language: str = DEFAULT_LANGUAGE) -> None:
# The three steps required to get access to call the API. # The three steps required to get access to call the API.
self._gateway = gateway self._gateway: core.Gateway = gateway
self._auth = auth self._auth: core.Auth = auth
self._session = session self._session: core.Session = session
# The last list of devices we got from the server. This is the # The last list of devices we got from the server. This is the
# raw JSON list data describing the devices. # raw JSON list data describing the devices.
self._devices = None self._devices: List[Dict[str, Any]] = []
# Cached model info data. This is a mapping from URLs to JSON # Cached model info data. This is a mapping from URLs to JSON
# responses. # responses.
self._model_info = {} self._model_info: Dict[str, Any] = {}
# Locale information used to discover a gateway, if necessary. # Locale information used to discover a gateway, if necessary.
self._country = country self._country: str = country
self._language = language self._language: str = language
@property @property
def gateway(self): def gateway(self) -> core.Gateway:
if not self._gateway: if not self._gateway:
self._gateway = core.Gateway.discover( self._gateway = core.Gateway.discover(
self._country, self._language self._country, self._language
@ -103,19 +104,19 @@ class Client(object):
return self._gateway return self._gateway
@property @property
def auth(self): def auth(self) -> core.Auth:
if not self._auth: if not self._auth:
assert False, "unauthenticated" assert False, "unauthenticated"
return self._auth return self._auth
@property @property
def session(self): def session(self) -> core.Session:
if not self._session: if not self._session:
self._session, self._devices = self.auth.start_session() self._session, self._devices = self.auth.start_session()
return self._session return self._session
@property @property
def devices(self): def devices(self) -> Generator['DeviceInfo', None, None]:
"""DeviceInfo objects describing the user's devices. """DeviceInfo objects describing the user's devices.
""" """
@ -123,7 +124,7 @@ class Client(object):
self._devices = self.session.get_devices() self._devices = self.session.get_devices()
return (DeviceInfo(d) for d in self._devices) return (DeviceInfo(d) for d in self._devices)
def get_device(self, device_id): def get_device(self, device_id) -> Optional['DeviceInfo']:
"""Look up a DeviceInfo object by device ID. """Look up a DeviceInfo object by device ID.
Return None if the device does not exist. Return None if the device does not exist.
@ -135,7 +136,7 @@ class Client(object):
return None return None
@classmethod @classmethod
def load(cls, state): def load(cls, state: Dict[str, Any]) -> 'Client':
"""Load a client from serialized state. """Load a client from serialized state.
""" """
@ -169,10 +170,10 @@ class Client(object):
return client return client
def dump(self): def dump(self) -> Dict[str, Any]:
"""Serialize the client state.""" """Serialize the client state."""
out = { out: Dict[str, Any] = {
'model_info': self._model_info, 'model_info': self._model_info,
} }
@ -199,12 +200,13 @@ class Client(object):
return out return out
def refresh(self): def refresh(self) -> None:
self._auth = self.auth.refresh() self._auth = self.auth.refresh()
self._session, self._devices = self.auth.start_session() self._session, self._devices = self.auth.start_session()
@classmethod @classmethod
def from_token(cls, refresh_token, country=None, language=None): def from_token(cls, refresh_token,
country=None, language=None) -> 'Client':
"""Construct a client using just a refresh token. """Construct a client using just a refresh token.
This allows simpler state storage (e.g., for human-written This allows simpler state storage (e.g., for human-written
@ -220,7 +222,7 @@ class Client(object):
client.refresh() client.refresh()
return client return client
def model_info(self, device): def model_info(self, device: 'DeviceInfo') -> 'ModelInfo':
"""For a DeviceInfo object, get a ModelInfo object describing """For a DeviceInfo object, get a ModelInfo object describing
the model's capabilities. the model's capabilities.
""" """
@ -266,27 +268,27 @@ class DeviceInfo(object):
This is populated from a JSON dictionary provided by the API. This is populated from a JSON dictionary provided by the API.
""" """
def __init__(self, data): def __init__(self, data: Dict[str, Any]) -> None:
self.data = data self.data = data
@property @property
def model_id(self): def model_id(self) -> str:
return self.data['modelNm'] return self.data['modelNm']
@property @property
def id(self): def id(self) -> str:
return self.data['deviceId'] return self.data['deviceId']
@property @property
def model_info_url(self): def model_info_url(self) -> str:
return self.data['modelJsonUrl'] return self.data['modelJsonUrl']
@property @property
def name(self): def name(self) -> str:
return self.data['alias'] return self.data['alias']
@property @property
def type(self): def type(self) -> DeviceType:
"""The kind of device, as a `DeviceType` value.""" """The kind of device, as a `DeviceType` value."""
return DeviceType(self.data['deviceType']) return DeviceType(self.data['deviceType'])

View File

@ -7,6 +7,7 @@ import hashlib
import hmac import hmac
import datetime import datetime
import requests import requests
from typing import Any, Dict, List
GATEWAY_URL = 'https://kic.lgthinq.com:46030/api/common/gatewayUriList' GATEWAY_URL = 'https://kic.lgthinq.com:46030/api/common/gatewayUriList'
APP_KEY = 'wideq' APP_KEY = 'wideq'
@ -19,11 +20,11 @@ OAUTH_CLIENT_KEY = 'LGAO221A02'
DATE_FORMAT = '%a, %d %b %Y %H:%M:%S +0000' DATE_FORMAT = '%a, %d %b %Y %H:%M:%S +0000'
def gen_uuid(): def gen_uuid() -> str:
return str(uuid.uuid4()) return str(uuid.uuid4())
def oauth2_signature(message, secret): def oauth2_signature(message: str, secret: str) -> bytes:
"""Get the base64-encoded SHA-1 HMAC digest of a string, as used in """Get the base64-encoded SHA-1 HMAC digest of a string, as used in
OAauth2 request signatures. OAauth2 request signatures.
@ -37,7 +38,7 @@ def oauth2_signature(message, secret):
return base64.b64encode(digest) return base64.b64encode(digest)
def get_list(obj, key): def get_list(obj, key: str) -> List[Dict[str, Any]]:
"""Look up a list using a key from an object. """Look up a list using a key from an object.
If `obj[key]` is a list, return it unchanged. If is something else, If `obj[key]` is a list, return it unchanged. If is something else,
@ -294,7 +295,7 @@ class Auth(object):
class Session(object): class Session(object):
def __init__(self, auth, session_id): def __init__(self, auth, session_id) -> None:
self.auth = auth self.auth = auth
self.session_id = session_id self.session_id = session_id
@ -308,7 +309,7 @@ class Session(object):
url = urljoin(self.auth.gateway.api_root + '/', path) url = urljoin(self.auth.gateway.api_root + '/', path)
return lgedm_post(url, data, self.auth.access_token, self.session_id) return lgedm_post(url, data, self.auth.access_token, self.session_id)
def get_devices(self): def get_devices(self) -> List[Dict[str, Any]]:
"""Get a list of devices associated with the user's account. """Get a list of devices associated with the user's account.
Return a list of dicts with information about the devices. Return a list of dicts with information about the devices.