Skip to content

Commit

Permalink
Add type information
Browse files Browse the repository at this point in the history
This helps later refactoring.
Use mypy to check types.
  • Loading branch information
gladhorn committed Jan 11, 2020
1 parent 09696d9 commit e98d2a1
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 39 deletions.
70 changes: 36 additions & 34 deletions wideq/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import requests
import base64
from collections import namedtuple
from typing import Any, Optional
from typing import Any, Dict, Generator, List, Optional

from . import core

Expand All @@ -25,17 +25,17 @@ class Monitor(object):
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.device_id = device_id

def start(self):
def start(self) -> None:
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)

def poll(self):
def poll(self) -> Optional[bytes]:
"""Get the current status data (a bytestring) or None if the
device is not yet ready.
"""
Expand All @@ -49,24 +49,24 @@ def poll(self):
return None

@staticmethod
def decode_json(data):
def decode_json(data: bytes) -> Dict[str, Any]:
"""Decode a bytestring that encodes JSON status data."""

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
decoded status result (or None if status is not available).
"""

data = self.poll()
return self.decode_json(data) if data else None

def __enter__(self):
def __enter__(self) -> 'Monitor':
self.start()
return self

def __exit__(self, type, value, tb):
def __exit__(self, type, value, tb) -> None:
self.stop()


Expand All @@ -76,54 +76,55 @@ class Client(object):
"""

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.
self._gateway = gateway
self._auth = auth
self._session = session
self._gateway: core.Gateway = gateway
self._auth: core.Auth = auth
self._session: core.Session = session

# The last list of devices we got from the server. This is the
# 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
# responses.
self._model_info = {}
self._model_info: Dict[str, Any] = {}

# Locale information used to discover a gateway, if necessary.
self._country = country
self._language = language
self._country: str = country
self._language: str = language

@property
def gateway(self):
def gateway(self) -> core.Gateway:
if not self._gateway:
self._gateway = core.Gateway.discover(
self._country, self._language
)
return self._gateway

@property
def auth(self):
def auth(self) -> core.Auth:
if not self._auth:
assert False, "unauthenticated"
return self._auth

@property
def session(self):
def session(self) -> core.Session:
if not self._session:
self._session, self._devices = self.auth.start_session()
return self._session

@property
def devices(self):
def devices(self) -> Generator['DeviceInfo', None, None]:
"""DeviceInfo objects describing the user's devices.
"""

if not self._devices:
self._devices = self.session.get_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.
Return None if the device does not exist.
Expand All @@ -135,7 +136,7 @@ def get_device(self, device_id):
return None

@classmethod
def load(cls, state):
def load(cls, state: Dict[str, Any]) -> 'Client':
"""Load a client from serialized state.
"""

Expand Down Expand Up @@ -169,10 +170,10 @@ def load(cls, state):

return client

def dump(self):
def dump(self) -> Dict[str, Any]:
"""Serialize the client state."""

out = {
out: Dict[str, Any] = {
'model_info': self._model_info,
}

Expand All @@ -199,12 +200,13 @@ def dump(self):

return out

def refresh(self):
def refresh(self) -> None:
self._auth = self.auth.refresh()
self._session, self._devices = self.auth.start_session()

@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.
This allows simpler state storage (e.g., for human-written
Expand All @@ -220,7 +222,7 @@ def from_token(cls, refresh_token, country=None, language=None):
client.refresh()
return client

def model_info(self, device):
def model_info(self, device: 'DeviceInfo') -> 'ModelInfo':
"""For a DeviceInfo object, get a ModelInfo object describing
the model's capabilities.
"""
Expand Down Expand Up @@ -266,27 +268,27 @@ class DeviceInfo(object):
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

@property
def model_id(self):
def model_id(self) -> str:
return self.data['modelNm']

@property
def id(self):
def id(self) -> str:
return self.data['deviceId']

@property
def model_info_url(self):
def model_info_url(self) -> str:
return self.data['modelJsonUrl']

@property
def name(self):
def name(self) -> str:
return self.data['alias']

@property
def type(self):
def type(self) -> DeviceType:
"""The kind of device, as a `DeviceType` value."""

return DeviceType(self.data['deviceType'])
Expand Down
11 changes: 6 additions & 5 deletions wideq/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import hmac
import datetime
import requests
from typing import Any, Dict, List

GATEWAY_URL = 'https://kic.lgthinq.com:46030/api/common/gatewayUriList'
APP_KEY = 'wideq'
Expand All @@ -19,11 +20,11 @@
DATE_FORMAT = '%a, %d %b %Y %H:%M:%S +0000'


def gen_uuid():
def gen_uuid() -> str:
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
OAauth2 request signatures.
Expand All @@ -37,7 +38,7 @@ def oauth2_signature(message, secret):
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.
If `obj[key]` is a list, return it unchanged. If is something else,
Expand Down Expand Up @@ -294,7 +295,7 @@ def refresh(self):


class Session(object):
def __init__(self, auth, session_id):
def __init__(self, auth, session_id) -> None:
self.auth = auth
self.session_id = session_id

Expand All @@ -308,7 +309,7 @@ def post(self, path, data=None):
url = urljoin(self.auth.gateway.api_root + '/', path)
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.
Return a list of dicts with information about the devices.
Expand Down

0 comments on commit e98d2a1

Please sign in to comment.