Skip to content

Commit

Permalink
add actions, types (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
lwestenberg authored Feb 23, 2022
1 parent f22e267 commit 7aa3263
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 77 deletions.
3 changes: 3 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ indent_style = space
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true

[*.yml,yaml,md]
indent_size = 2
25 changes: 25 additions & 0 deletions .github/workflows/build_release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: build

on: push

jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Python 3.9
uses: actions/setup-python@v1
with:
python-version: 3.9
- name: Install requirements
run: pip install --upgrade . build twine
- name: Run unit tests
run: python -m pytest --asyncio-mode=auto
- name: Build
run: python -m build
- name: Publish distribution 📦 to PyPI
if: startsWith(github.ref, 'refs/tags')
uses: pypa/gh-action-pypi-publish@master
with:
password: ${{ secrets.PYPI_API_TOKEN }}
47 changes: 25 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
# Bold Smart Lock Python Package

This package implements the Bold Smart Lock API to authenticate and unlock a Bold smart lock. Usage of this API requires a Bold Connect.

## Installation
To install dependencies during development run `pip3 install .` from the project directory.

To install dependencies during development run `pip install .` from the project directory.

## Run tests
Run `python3 -m pytest --asyncio-mode=auto` from the project directory.

Run `python -m pytest --asyncio-mode=auto` from the project directory.

## Usage

Expand All @@ -16,32 +19,32 @@ import aiohttp
from bold_smart_lock.bold_smart_lock import BoldSmartLock

async def main():
async with aiohttp.ClientSession() as session:
bold = BoldSmartLock(session)
async with aiohttp.ClientSession() as session:
bold = BoldSmartLock(session)

# Request a validation code for the account e-mail address
verify_email_response = await bold.verify_email("[email protected]");
print(verify_email_response)
# Request a validation id and validation code for the account e-mail address
request_validation_id_response = await bold.request_validation_id("[email protected]");
print(request_validation_id_response)

# Authenticate with email, password, validation code (from email) and validation id (from output)
authenticate_response = await bold.authenticate("[email protected]", "password", "01234", "00000000-0000-0000-0000-000000000000");
print(authenticate_response)
# Authenticate with email, password, validation code (from email) and validation id (from output)
authenticate_response = await bold.authenticate("[email protected]", "password", "01234", "00000000-0000-0000-0000-000000000000");
print(authenticate_response)

# E.g. for testing purpose you can set a token
token = "00000000-0000-0000-0000-000000000000"
bold.set_token(token)
# E.g. for testing purpose you can set a token
token = "00000000-0000-0000-0000-000000000000"
bold.set_token(token)

# Get the devices and device permissions
get_device_permissions_response = await bold.get_device_permissions()
print(get_device_permissions_response)
# Get the devices and device permissions
get_device_permissions_response = await bold.get_device_permissions()
print(get_device_permissions_response)

# Active the smart lock by device id
remote_activation_response = await bold.remote_activation(12345)
print(remote_activation_response)
# Active the smart lock by device id
remote_activation_response = await bold.remote_activation(12345)
print(remote_activation_response)

# Re-login / update token
relogin_response = await bold.re_login()
print(relogin_response)
# Re-login / update token
relogin_response = await bold.re_login()
print(relogin_response)

asyncio.run(main())
```
88 changes: 47 additions & 41 deletions bold_smart_lock/auth.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,38 @@
"""Bold Smart Lock authentication."""
import aiohttp
from __future__ import annotations
from aiohttp.web import HTTPUnauthorized
from bold_smart_lock.exceptions import AuthenticateFailed, EmailOrPhoneNotSpecified, InvalidEmail, InvalidPhone, InvalidValidationCode, InvalidValidationId, InvalidValidationResponse, MissingValidationId, TokenMissing, VerificationNotFound

from .const import (
API_URI,
INVALID_EMAIL_ERROR,
INVALID_PHONE_ERROR,
POST_HEADERS,
VALIDATIONS_ENDPOINT,
AUTHENTICATIONS_ENDPOINT,
AUTHENTICATION_REQUEST_JSON,
)
import aiohttp


class Auth:
"""Authorization class for Bold Smart Lock"""

def __init__(self, session: aiohttp.ClientSession):
self._session = session
self._token = None
self._validation_id = None
self._token: str = None
self._validation_id: str = None

async def authenticate(
self, email: str, password: str, verification_code: str, validation_id: str
self, email: str, password: str, verification_code: str, validation_id: str, language: str = "en"
):
"""Authenticate with the login details, validation_id and validation_code"""
verified = await self.__verify_validation_id(verification_code, validation_id)

if verified and email and password and self._validation_id:
request_json = AUTHENTICATION_REQUEST_JSON
request_json["validationId"] = self._validation_id
request_json = {
"language": language,
"clientLocale": "en-US",
"validationId": self._validation_id,
}
headers = self.headers()

try:
Expand All @@ -39,13 +41,14 @@ async def authenticate(
headers=headers,
auth=aiohttp.BasicAuth(email, password),
json=request_json,
raise_for_status=False
) as response:
if response.status == 401:
raise HTTPUnauthorized
elif response.status == 404:
raise VerificationNotFound
elif response.content_type == "application/json":
response_json = await response.json()
response_json: dict[str, str] = await response.json()
if "token" in response_json:
self.set_token(response_json["token"])
return response_json
Expand All @@ -68,8 +71,9 @@ async def re_login(self):
async with self._session.put(
API_URI + AUTHENTICATIONS_ENDPOINT + "/" + self.token(),
headers=self.headers(),
raise_for_status=False
) as response:
response_json = await response.json()
response_json: dict[str, str] = await response.json()

if "token" in response_json:
self.set_token(response_json["token"])
Expand All @@ -87,32 +91,34 @@ async def request_validation_id(self, email: str = None, phone: str = None):

if request_json:
try:
response = await self._session.post(
API_URI + VALIDATIONS_ENDPOINT, json=request_json, headers=self.headers()
)
async with self._session.post(
API_URI + VALIDATIONS_ENDPOINT,
json=request_json,
headers=self.headers(),
raise_for_status=False
) as response:
if response.content_type == "application/json":
response_json: dict[str, str] = await response.json()
if "errorCode" in response_json:
if response_json["errorCode"] == INVALID_EMAIL_ERROR:
raise InvalidEmail
elif response_json["errorCode"] == INVALID_PHONE_ERROR:
raise InvalidPhone
elif response_json.status == 400:
raise EmailOrPhoneNotSpecified

if "id" in response_json:
self._validation_id = response_json["id"]
return response_json
except Exception as exception:
raise exception
else:
if response.content_type == "application/json":
response_json = await response.json()
if "errorCode" in response_json:
if response_json["errorCode"] == INVALID_EMAIL_ERROR:
raise InvalidEmail
elif response_json["errorCode"] == INVALID_PHONE_ERROR:
raise InvalidPhone
elif response_json.status == 400:
raise EmailOrPhoneNotSpecified

if "id" in response_json:
self._validation_id = response_json["id"]
return response_json
raise EmailOrPhoneNotSpecified

def set_token(self, token: str):
"""Update the token"""
self._token = token

def token(self) -> str:
def token(self):
"""Get the token and update it when needed"""
if self._token:
return self._token
Expand All @@ -126,23 +132,23 @@ async def __verify_validation_id(

if self._validation_id and verification_code:
try:
response = await self._session.post(
async with await self._session.post(
API_URI + VALIDATIONS_ENDPOINT + "/" + self._validation_id,
json={"code": verification_code},
headers=self.headers(),
)
raise_for_status=False
) as response:
if response.status == 200:
return True
if response.status == 400:
raise InvalidValidationCode
elif response.status == 404:
raise InvalidValidationId
elif response.status == 405:
raise MissingValidationId
else:
raise InvalidValidationResponse
except Exception as exception:
raise exception
else:
if response.status == 200:
return True
if response.status == 400:
raise InvalidValidationCode
elif response.status == 404:
raise InvalidValidationId
elif response.status == 405:
raise MissingValidationId
else:
raise InvalidValidationResponse

return False
13 changes: 6 additions & 7 deletions bold_smart_lock/bold_smart_lock.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
"""Bold Smart Lock API wrapper"""
import aiohttp

from __future__ import annotations
from .auth import Auth
from .const import (
API_URI,
REMOTE_ACTIVATION_ENDPOINT,
EFFECTIVE_DEVICE_PERMISSIONS_ENDPOINT,
)

from .auth import Auth

import aiohttp

class BoldSmartLock:
"""A Python Abstraction object to Bold Smart Lock"""
Expand Down Expand Up @@ -38,7 +36,8 @@ async def get_device_permissions(self):
async with self._session.get(
API_URI + EFFECTIVE_DEVICE_PERMISSIONS_ENDPOINT, headers=headers, raise_for_status=True
) as response:
return await response.json()
response_json: dict[str, str] = await response.json()
return response_json
except Exception as exception:
raise exception

Expand All @@ -54,7 +53,7 @@ async def remote_activation(self, device_id: int):
async with self._session.post(
API_URI + REMOTE_ACTIVATION_ENDPOINT.format(device_id), headers=headers, raise_for_status=True
) as response:
response_json = await response.json()
response_json: dict[str, str] = await response.json()
return response_json
except Exception as exception:
raise exception
Expand Down
8 changes: 1 addition & 7 deletions bold_smart_lock/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,6 @@
# Default headers
POST_HEADERS = {"Content-Type": "application/json"}

# Default JSON request data
AUTHENTICATION_REQUEST_JSON = {
"language": "en",
"clientLocale": "en-US",
}

# Response error codes
# Response body error codes
INVALID_EMAIL_ERROR = "invalidEmailError"
INVALID_PHONE_ERROR = "invalidPhoneError"

0 comments on commit 7aa3263

Please sign in to comment.