libdyson-rest 0.3.0__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.
- libdyson_rest-0.3.0/.flake8 +21 -0
- libdyson_rest-0.3.0/LICENSE +21 -0
- libdyson_rest-0.3.0/MANIFEST.in +34 -0
- libdyson_rest-0.3.0/PKG-INFO +604 -0
- libdyson_rest-0.3.0/README.md +563 -0
- libdyson_rest-0.3.0/pyproject.toml +129 -0
- libdyson_rest-0.3.0/requirements-dev.txt +15 -0
- libdyson_rest-0.3.0/requirements.txt +2 -0
- libdyson_rest-0.3.0/setup.cfg +4 -0
- libdyson_rest-0.3.0/src/libdyson_rest/__init__.py +70 -0
- libdyson_rest-0.3.0/src/libdyson_rest/client.py +497 -0
- libdyson_rest-0.3.0/src/libdyson_rest/exceptions.py +33 -0
- libdyson_rest-0.3.0/src/libdyson_rest/models/__init__.py +122 -0
- libdyson_rest-0.3.0/src/libdyson_rest/models/auth.py +94 -0
- libdyson_rest-0.3.0/src/libdyson_rest/models/device.py +172 -0
- libdyson_rest-0.3.0/src/libdyson_rest/models/iot.py +64 -0
- libdyson_rest-0.3.0/src/libdyson_rest/utils/__init__.py +78 -0
- libdyson_rest-0.3.0/src/libdyson_rest.egg-info/PKG-INFO +604 -0
- libdyson_rest-0.3.0/src/libdyson_rest.egg-info/SOURCES.txt +20 -0
- libdyson_rest-0.3.0/src/libdyson_rest.egg-info/dependency_links.txt +1 -0
- libdyson_rest-0.3.0/src/libdyson_rest.egg-info/requires.txt +13 -0
- libdyson_rest-0.3.0/src/libdyson_rest.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
[flake8]
|
|
2
|
+
max-line-length = 120
|
|
3
|
+
extend-ignore =
|
|
4
|
+
# E203: whitespace before ':' (conflicts with black)
|
|
5
|
+
E203,
|
|
6
|
+
# W503: line break before binary operator (conflicts with black)
|
|
7
|
+
W503,
|
|
8
|
+
exclude =
|
|
9
|
+
.git,
|
|
10
|
+
__pycache__,
|
|
11
|
+
.venv,
|
|
12
|
+
venv,
|
|
13
|
+
build,
|
|
14
|
+
dist,
|
|
15
|
+
.tox,
|
|
16
|
+
.eggs,
|
|
17
|
+
*.egg-info
|
|
18
|
+
per-file-ignores =
|
|
19
|
+
# Allow unused imports in __init__.py files
|
|
20
|
+
__init__.py:F401
|
|
21
|
+
max-complexity = 10
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Christopher Gray
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Include the README and license files
|
|
2
|
+
include README.md
|
|
3
|
+
include LICENSE
|
|
4
|
+
|
|
5
|
+
# Include requirements files
|
|
6
|
+
include requirements.txt
|
|
7
|
+
include requirements-dev.txt
|
|
8
|
+
|
|
9
|
+
# Include configuration files
|
|
10
|
+
include pyproject.toml
|
|
11
|
+
include .flake8
|
|
12
|
+
|
|
13
|
+
# Include the source code
|
|
14
|
+
recursive-include src *.py
|
|
15
|
+
|
|
16
|
+
# Include type stubs if any
|
|
17
|
+
recursive-include src *.pyi
|
|
18
|
+
|
|
19
|
+
# Exclude development and build files
|
|
20
|
+
exclude .gitignore
|
|
21
|
+
exclude .pre-commit-config.yaml
|
|
22
|
+
recursive-exclude * __pycache__
|
|
23
|
+
recursive-exclude * *.py[co]
|
|
24
|
+
recursive-exclude * *.so
|
|
25
|
+
recursive-exclude * *.dylib
|
|
26
|
+
recursive-exclude .venv *
|
|
27
|
+
recursive-exclude venv *
|
|
28
|
+
recursive-exclude build *
|
|
29
|
+
recursive-exclude dist *
|
|
30
|
+
recursive-exclude *.egg-info *
|
|
31
|
+
recursive-exclude tests *
|
|
32
|
+
recursive-exclude examples *
|
|
33
|
+
recursive-exclude .git *
|
|
34
|
+
recursive-exclude .github *
|
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: libdyson-rest
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Python library for interacting with Dyson devices through their official REST API
|
|
5
|
+
Author-email: Chris Gray <cmgrayb@outlook.com>
|
|
6
|
+
Maintainer-email: Chris Gray <cmgrayb@outlook.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/cmgrayb/libdyson-rest
|
|
9
|
+
Project-URL: Repository, https://github.com/cmgrayb/libdyson-rest
|
|
10
|
+
Project-URL: Issues, https://github.com/cmgrayb/libdyson-rest/issues
|
|
11
|
+
Project-URL: Documentation, https://github.com/cmgrayb/libdyson-rest#readme
|
|
12
|
+
Project-URL: Changelog, https://github.com/cmgrayb/libdyson-rest/releases
|
|
13
|
+
Keywords: dyson,air-purifier,fan,iot,rest-api,mqtt,home-automation
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Home Automation
|
|
23
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
+
Requires-Python: >=3.8
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: requests>=2.28.0
|
|
29
|
+
Requires-Dist: cryptography>=3.4.0
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: black==25.1.0; extra == "dev"
|
|
32
|
+
Requires-Dist: flake8==7.3.0; extra == "dev"
|
|
33
|
+
Requires-Dist: isort==6.0.1; extra == "dev"
|
|
34
|
+
Requires-Dist: pytest==8.4.1; extra == "dev"
|
|
35
|
+
Requires-Dist: pytest-cov==6.2.1; extra == "dev"
|
|
36
|
+
Requires-Dist: mypy==1.17.1; extra == "dev"
|
|
37
|
+
Requires-Dist: types-requests==2.32.4.20250809; extra == "dev"
|
|
38
|
+
Requires-Dist: types-cryptography==3.3.23.2; extra == "dev"
|
|
39
|
+
Requires-Dist: bandit[toml]==1.8.0; extra == "dev"
|
|
40
|
+
Dynamic: license-file
|
|
41
|
+
|
|
42
|
+
# libdyson-rest
|
|
43
|
+
|
|
44
|
+
[](https://badge.fury.io/py/libdyson-rest)
|
|
45
|
+
[](https://pypi.org/project/libdyson-rest/)
|
|
46
|
+
[](https://github.com/cmgrayb/libdyson-rest/blob/main/LICENSE)
|
|
47
|
+
|
|
48
|
+
A Python library for interacting with Dyson devices through their official REST API.
|
|
49
|
+
|
|
50
|
+
## Features
|
|
51
|
+
|
|
52
|
+
- **Official API Compliance**: Implements the complete Dyson App API as documented in their OpenAPI specification
|
|
53
|
+
- **Two-Step Authentication**: Secure login process with OTP codes
|
|
54
|
+
- **Complete Device Management**: List devices, get device details, and retrieve IoT credentials
|
|
55
|
+
- **MQTT Connection Support**: Extract both cloud (AWS IoT) and local MQTT connection parameters
|
|
56
|
+
- **Password Decryption**: Decrypt local MQTT broker credentials for direct device communication
|
|
57
|
+
- **Token-Based Authentication**: Store and reuse authentication tokens for repeated API calls
|
|
58
|
+
- **Type-Safe Models**: Comprehensive data models with proper type hints
|
|
59
|
+
- **Error Handling**: Detailed exception hierarchy for robust error handling
|
|
60
|
+
- **Context Manager Support**: Automatic resource cleanup
|
|
61
|
+
|
|
62
|
+
## Installation
|
|
63
|
+
|
|
64
|
+
Install from PyPI:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install libdyson-rest
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Or install from source:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
git clone https://github.com/cmgrayb/libdyson-rest.git
|
|
74
|
+
cd libdyson-rest
|
|
75
|
+
pip install -e .
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Quick Start
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from libdyson_rest import DysonClient
|
|
82
|
+
|
|
83
|
+
# Initialize the client
|
|
84
|
+
client = DysonClient(
|
|
85
|
+
email="your@email.com",
|
|
86
|
+
password="your_password",
|
|
87
|
+
country="US", # ISO 3166-1 alpha-2 country code
|
|
88
|
+
culture="en-US" # IETF language code
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Two-step authentication process
|
|
92
|
+
try:
|
|
93
|
+
# Step 1: Begin login process
|
|
94
|
+
challenge = client.begin_login()
|
|
95
|
+
print(f"Challenge ID: {challenge.challenge_id}")
|
|
96
|
+
print("Check your email for an OTP code")
|
|
97
|
+
|
|
98
|
+
# Step 2: Complete login with OTP code
|
|
99
|
+
otp_code = input("Enter OTP code: ")
|
|
100
|
+
login_info = client.complete_login(str(challenge.challenge_id), otp_code)
|
|
101
|
+
print(f"Logged in! Account: {login_info.account}")
|
|
102
|
+
|
|
103
|
+
# Get devices
|
|
104
|
+
devices = client.get_devices()
|
|
105
|
+
for device in devices:
|
|
106
|
+
print(f"Device: {device.name} ({device.serial_number})")
|
|
107
|
+
print(f" Type: {device.type}")
|
|
108
|
+
print(f" Category: {device.category.value}")
|
|
109
|
+
|
|
110
|
+
# Get IoT credentials for connected devices
|
|
111
|
+
if device.connection_category.value != "nonConnected":
|
|
112
|
+
iot_data = client.get_iot_credentials(device.serial_number)
|
|
113
|
+
print(f" IoT Endpoint: {iot_data.endpoint}")
|
|
114
|
+
|
|
115
|
+
finally:
|
|
116
|
+
client.close()
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Authentication Flow
|
|
120
|
+
|
|
121
|
+
The Dyson API uses a secure two-step authentication process:
|
|
122
|
+
|
|
123
|
+
### 1. API Provisioning (Automatic)
|
|
124
|
+
```python
|
|
125
|
+
version = client.provision() # Called automatically
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 2. User Status Check (Optional)
|
|
129
|
+
```python
|
|
130
|
+
user_status = client.get_user_status()
|
|
131
|
+
print(f"Account status: {user_status.account_status.value}")
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### 3. Begin Login Process
|
|
135
|
+
```python
|
|
136
|
+
challenge = client.begin_login()
|
|
137
|
+
# This triggers an OTP code to be sent to your email
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### 4. Complete Login with OTP
|
|
141
|
+
```python
|
|
142
|
+
login_info = client.complete_login(
|
|
143
|
+
challenge_id=str(challenge.challenge_id),
|
|
144
|
+
otp_code="123456" # From your email
|
|
145
|
+
)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### 5. Authenticated API Calls
|
|
149
|
+
```python
|
|
150
|
+
devices = client.get_devices()
|
|
151
|
+
iot_data = client.get_iot_credentials("device_serial")
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## API Reference
|
|
155
|
+
|
|
156
|
+
### DysonClient
|
|
157
|
+
|
|
158
|
+
#### Constructor
|
|
159
|
+
```python
|
|
160
|
+
DysonClient(
|
|
161
|
+
email: Optional[str] = None,
|
|
162
|
+
password: Optional[str] = None,
|
|
163
|
+
country: str = "US",
|
|
164
|
+
culture: str = "en-US",
|
|
165
|
+
timeout: int = 30,
|
|
166
|
+
user_agent: str = "android client"
|
|
167
|
+
)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
#### Core Methods
|
|
171
|
+
|
|
172
|
+
##### Authentication
|
|
173
|
+
- `provision() -> str`: Required initial API call
|
|
174
|
+
- `get_user_status(email=None) -> UserStatus`: Check account status
|
|
175
|
+
- `begin_login(email=None) -> LoginChallenge`: Start login process
|
|
176
|
+
- `complete_login(challenge_id, otp_code, email=None, password=None) -> LoginInformation`: Complete authentication
|
|
177
|
+
- `authenticate(otp_code=None) -> bool`: Convenience method for full auth flow
|
|
178
|
+
|
|
179
|
+
##### Device Management
|
|
180
|
+
- `get_devices() -> List[Device]`: List all account devices
|
|
181
|
+
- `get_iot_credentials(serial_number) -> IoTData`: Get AWS IoT connection info
|
|
182
|
+
|
|
183
|
+
##### Session Management
|
|
184
|
+
- `close() -> None`: Close session and clear state
|
|
185
|
+
- `__enter__()` and `__exit__()`: Context manager support
|
|
186
|
+
|
|
187
|
+
### Data Models
|
|
188
|
+
|
|
189
|
+
#### Device
|
|
190
|
+
```python
|
|
191
|
+
@dataclass
|
|
192
|
+
class Device:
|
|
193
|
+
category: DeviceCategory # ec, flrc, hc, light, robot, wearable
|
|
194
|
+
connection_category: ConnectionCategory # lecAndWifi, lecOnly, nonConnected, wifiOnly
|
|
195
|
+
model: str
|
|
196
|
+
name: str
|
|
197
|
+
serial_number: str
|
|
198
|
+
type: str
|
|
199
|
+
variant: Optional[str] = None
|
|
200
|
+
connected_configuration: Optional[ConnectedConfiguration] = None
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
#### DeviceCategory (Enum)
|
|
204
|
+
- `ENVIRONMENT_CLEANER = "ec"` - Air filters, purifiers
|
|
205
|
+
- `FLOOR_CLEANER = "flrc"` - Vacuum cleaners
|
|
206
|
+
- `HAIR_CARE = "hc"` - Hair dryers, stylers
|
|
207
|
+
- `LIGHT = "light"` - Lighting products
|
|
208
|
+
- `ROBOT = "robot"` - Robot vacuums
|
|
209
|
+
- `WEARABLE = "wearable"` - Wearable devices
|
|
210
|
+
|
|
211
|
+
#### ConnectionCategory (Enum)
|
|
212
|
+
- `LEC_AND_WIFI = "lecAndWifi"` - Bluetooth and Wi-Fi
|
|
213
|
+
- `LEC_ONLY = "lecOnly"` - Bluetooth only
|
|
214
|
+
- `NON_CONNECTED = "nonConnected"` - No connectivity
|
|
215
|
+
- `WIFI_ONLY = "wifiOnly"` - Wi-Fi only
|
|
216
|
+
|
|
217
|
+
#### LoginInformation
|
|
218
|
+
```python
|
|
219
|
+
@dataclass
|
|
220
|
+
class LoginInformation:
|
|
221
|
+
account: UUID # Account ID
|
|
222
|
+
token: str # Bearer token for API calls
|
|
223
|
+
token_type: TokenType # Always "Bearer"
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
#### IoTData
|
|
227
|
+
```python
|
|
228
|
+
@dataclass
|
|
229
|
+
class IoTData:
|
|
230
|
+
endpoint: str # AWS IoT endpoint
|
|
231
|
+
iot_credentials: IoTCredentials # Connection credentials
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Exception Hierarchy
|
|
235
|
+
|
|
236
|
+
```
|
|
237
|
+
DysonAPIError (base)
|
|
238
|
+
├── DysonConnectionError # Network/connection issues
|
|
239
|
+
├── DysonAuthError # Authentication failures
|
|
240
|
+
├── DysonDeviceError # Device operation failures
|
|
241
|
+
└── DysonValidationError # Input validation errors
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## Advanced Usage
|
|
245
|
+
|
|
246
|
+
### Using Context Manager
|
|
247
|
+
```python
|
|
248
|
+
with DysonClient(email="your@email.com", password="password") as client:
|
|
249
|
+
# Authentication
|
|
250
|
+
challenge = client.begin_login()
|
|
251
|
+
otp = input("Enter OTP: ")
|
|
252
|
+
client.complete_login(str(challenge.challenge_id), otp)
|
|
253
|
+
|
|
254
|
+
# API calls
|
|
255
|
+
devices = client.get_devices()
|
|
256
|
+
# Client automatically closed on exit
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Error Handling
|
|
260
|
+
```python
|
|
261
|
+
from libdyson_rest import DysonAuthError, DysonConnectionError, DysonAPIError
|
|
262
|
+
|
|
263
|
+
try:
|
|
264
|
+
client = DysonClient(email="user@example.com", password="pass")
|
|
265
|
+
challenge = client.begin_login()
|
|
266
|
+
|
|
267
|
+
except DysonAuthError as e:
|
|
268
|
+
print(f"Authentication failed: {e}")
|
|
269
|
+
except DysonConnectionError as e:
|
|
270
|
+
print(f"Network error: {e}")
|
|
271
|
+
except DysonAPIError as e:
|
|
272
|
+
print(f"API error: {e}")
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Manual Authentication Steps
|
|
276
|
+
```python
|
|
277
|
+
client = DysonClient(email="user@example.com", password="password")
|
|
278
|
+
|
|
279
|
+
# Step 1: Provision (required)
|
|
280
|
+
version = client.provision()
|
|
281
|
+
print(f"API version: {version}")
|
|
282
|
+
|
|
283
|
+
# Step 2: Check user status
|
|
284
|
+
user_status = client.get_user_status()
|
|
285
|
+
print(f"Account active: {user_status.account_status.value == 'ACTIVE'}")
|
|
286
|
+
|
|
287
|
+
# Step 3: Begin login
|
|
288
|
+
challenge = client.begin_login()
|
|
289
|
+
print("Check email for OTP")
|
|
290
|
+
|
|
291
|
+
# Step 4: Complete login
|
|
292
|
+
otp = input("OTP: ")
|
|
293
|
+
login_info = client.complete_login(str(challenge.challenge_id), otp)
|
|
294
|
+
print(f"Bearer token: {login_info.token[:10]}...")
|
|
295
|
+
|
|
296
|
+
# Step 5: Use authenticated endpoints
|
|
297
|
+
devices = client.get_devices()
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## Configuration
|
|
301
|
+
|
|
302
|
+
### Environment Variables
|
|
303
|
+
- `DYSON_EMAIL`: Default email address
|
|
304
|
+
- `DYSON_PASSWORD`: Default password
|
|
305
|
+
- `DYSON_COUNTRY`: Default country code (default: "US")
|
|
306
|
+
- `DYSON_CULTURE`: Default culture/locale (default: "en-US")
|
|
307
|
+
- `DYSON_TIMEOUT`: Request timeout in seconds (default: "30")
|
|
308
|
+
|
|
309
|
+
### Country and Culture Codes
|
|
310
|
+
- **Country**: 2-letter uppercase ISO 3166-1 alpha-2 codes (e.g., "US", "GB", "DE")
|
|
311
|
+
- **Culture**: 5-character IETF language codes (e.g., "en-US", "en-GB", "de-DE")
|
|
312
|
+
|
|
313
|
+
## API Compliance
|
|
314
|
+
|
|
315
|
+
This library implements the complete Dyson App API as documented in their OpenAPI specification:
|
|
316
|
+
- Authentication endpoints (`/v3/userregistration/email/*`)
|
|
317
|
+
- Device management (`/v3/manifest`)
|
|
318
|
+
- IoT credentials (`/v2/authorize/iot-credentials`)
|
|
319
|
+
- Provisioning (`/v1/provisioningservice/application/Android/version`)
|
|
320
|
+
|
|
321
|
+
## Requirements
|
|
322
|
+
|
|
323
|
+
- Python 3.8+
|
|
324
|
+
- `requests` - HTTP client library
|
|
325
|
+
- `dataclasses` - Data model support (Python 3.8+)
|
|
326
|
+
|
|
327
|
+
## Contributing
|
|
328
|
+
|
|
329
|
+
Contributions are welcome! Please ensure all changes maintain compatibility with the official Dyson OpenAPI specification.
|
|
330
|
+
|
|
331
|
+
## Versioning & Releases
|
|
332
|
+
|
|
333
|
+
This project follows **PEP 440** versioning (not semantic versioning). Here's how versions are distributed:
|
|
334
|
+
|
|
335
|
+
### Version Patterns
|
|
336
|
+
|
|
337
|
+
| Pattern | Example | Distribution | Purpose |
|
|
338
|
+
|---------|---------|--------------|---------|
|
|
339
|
+
| **Alpha** | `0.3.0a1`, `0.3.0alpha1` | TestPyPI | Internal testing only |
|
|
340
|
+
| **Dev** | `0.3.0.dev1` | TestPyPI | Development builds |
|
|
341
|
+
| **Beta** | `0.3.0b1`, `0.3.0beta1` | **PyPI** | Public beta testing |
|
|
342
|
+
| **RC** | `0.3.0rc1` | **PyPI** | Release candidates |
|
|
343
|
+
| **Stable** | `0.3.0` | **PyPI** | Production releases |
|
|
344
|
+
| **Patch** | `0.3.0.post1` | **PyPI** | Post-release patches |
|
|
345
|
+
|
|
346
|
+
### Installation
|
|
347
|
+
|
|
348
|
+
```bash
|
|
349
|
+
# Install stable release
|
|
350
|
+
pip install libdyson-rest
|
|
351
|
+
|
|
352
|
+
# Install latest beta (includes rc, beta versions)
|
|
353
|
+
pip install --pre libdyson-rest
|
|
354
|
+
|
|
355
|
+
# Install specific version
|
|
356
|
+
pip install libdyson-rest==0.3.0b1
|
|
357
|
+
|
|
358
|
+
# Install from TestPyPI (alpha/dev versions)
|
|
359
|
+
pip install -i https://test.pypi.org/simple/ libdyson-rest==0.3.0a1
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### For Beta Testers
|
|
363
|
+
|
|
364
|
+
Want to help test new features? Install pre-release versions:
|
|
365
|
+
|
|
366
|
+
```bash
|
|
367
|
+
pip install --pre libdyson-rest
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
This will install the latest beta or release candidate, giving you access to new features before stable release.
|
|
371
|
+
|
|
372
|
+
## License
|
|
373
|
+
|
|
374
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
375
|
+
|
|
376
|
+
## Disclaimer
|
|
377
|
+
|
|
378
|
+
This is an unofficial library. Dyson is a trademark of Dyson Ltd. This library is not affiliated with, endorsed by, or sponsored by Dyson Ltd.
|
|
379
|
+
|
|
380
|
+
## OpenAPI Specification
|
|
381
|
+
|
|
382
|
+
This library is based on the community-documented Dyson App API OpenAPI specification. The specification can be found at:
|
|
383
|
+
https://raw.githubusercontent.com/libdyson-wg/appapi/refs/heads/main/openapi.yaml
|
|
384
|
+
|
|
385
|
+
This project is created to further the efforts of others in the community in interacting with the
|
|
386
|
+
Dyson devices they have purchased to better integrate them into their smart homes.
|
|
387
|
+
|
|
388
|
+
At this time, this library is PURELY EXPERIMENTAL and should not be used without carefully examining
|
|
389
|
+
the code before doing so. **USE AT YOUR OWN RISK**
|
|
390
|
+
|
|
391
|
+
## Features
|
|
392
|
+
|
|
393
|
+
- Clean, intuitive API for Dyson device interaction
|
|
394
|
+
- Full type hints support
|
|
395
|
+
- Comprehensive error handling
|
|
396
|
+
- Async/sync support
|
|
397
|
+
- Built-in authentication handling
|
|
398
|
+
- Extensive test coverage
|
|
399
|
+
|
|
400
|
+
## Installation
|
|
401
|
+
|
|
402
|
+
### From Source (Development)
|
|
403
|
+
|
|
404
|
+
```bash
|
|
405
|
+
# Clone the repository
|
|
406
|
+
git clone https://github.com/cmgrayb/libdyson-rest.git
|
|
407
|
+
cd libdyson-rest
|
|
408
|
+
|
|
409
|
+
# Create and activate virtual environment
|
|
410
|
+
python -m venv .venv
|
|
411
|
+
|
|
412
|
+
# Windows
|
|
413
|
+
.venv\Scripts\activate
|
|
414
|
+
|
|
415
|
+
# Linux/Mac
|
|
416
|
+
source .venv/bin/activate
|
|
417
|
+
|
|
418
|
+
# Install development dependencies
|
|
419
|
+
pip install -r requirements-dev.txt
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
## Quick Start
|
|
423
|
+
|
|
424
|
+
```python
|
|
425
|
+
from libdyson_rest import DysonClient
|
|
426
|
+
|
|
427
|
+
# Initialize the client
|
|
428
|
+
client = DysonClient(
|
|
429
|
+
email="your_email@example.com",
|
|
430
|
+
password="your_password",
|
|
431
|
+
country="US"
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
# Authenticate with Dyson API
|
|
435
|
+
client.authenticate()
|
|
436
|
+
|
|
437
|
+
# Get your devices
|
|
438
|
+
devices = client.get_devices()
|
|
439
|
+
for device in devices:
|
|
440
|
+
print(f"Device: {device['name']} ({device['serial']})")
|
|
441
|
+
|
|
442
|
+
# Always close the client when done
|
|
443
|
+
client.close()
|
|
444
|
+
|
|
445
|
+
# Or use as context manager
|
|
446
|
+
with DysonClient(email="email@example.com", password="password") as client:
|
|
447
|
+
client.authenticate()
|
|
448
|
+
devices = client.get_devices()
|
|
449
|
+
# Client is automatically closed
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
## Development
|
|
453
|
+
|
|
454
|
+
This project uses several tools to maintain code quality:
|
|
455
|
+
|
|
456
|
+
- **Black**: Code formatting (120 character line length)
|
|
457
|
+
- **Flake8**: Linting and style checking
|
|
458
|
+
- **isort**: Import sorting
|
|
459
|
+
- **MyPy**: Type checking
|
|
460
|
+
- **Pytest**: Testing framework
|
|
461
|
+
- **Pre-commit**: Git hooks
|
|
462
|
+
|
|
463
|
+
### Setting up Development Environment
|
|
464
|
+
|
|
465
|
+
1. **Create virtual environment and install dependencies:**
|
|
466
|
+
```bash
|
|
467
|
+
python -m venv .venv
|
|
468
|
+
.venv\Scripts\activate # Windows
|
|
469
|
+
pip install -r requirements-dev.txt
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
2. **Install pre-commit hooks:**
|
|
473
|
+
```bash
|
|
474
|
+
pre-commit install
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### VSCode Tasks
|
|
478
|
+
|
|
479
|
+
This project includes VSCode tasks for common development operations:
|
|
480
|
+
|
|
481
|
+
- **Setup Dev Environment**: Create venv and install dependencies
|
|
482
|
+
- **Format Code**: Run Black formatter
|
|
483
|
+
- **Lint Code**: Run Flake8 linter
|
|
484
|
+
- **Sort Imports**: Run isort
|
|
485
|
+
- **Type Check**: Run MyPy type checker
|
|
486
|
+
- **Run Tests**: Execute pytest with coverage
|
|
487
|
+
- **Check All**: Run all quality checks in sequence
|
|
488
|
+
|
|
489
|
+
Access these via `Ctrl+Shift+P` → "Tasks: Run Task"
|
|
490
|
+
|
|
491
|
+
### Code Quality Commands
|
|
492
|
+
|
|
493
|
+
```bash
|
|
494
|
+
# Format code
|
|
495
|
+
black .
|
|
496
|
+
|
|
497
|
+
# Sort imports
|
|
498
|
+
isort .
|
|
499
|
+
|
|
500
|
+
# Lint code
|
|
501
|
+
flake8 .
|
|
502
|
+
|
|
503
|
+
# Type check
|
|
504
|
+
mypy src/libdyson_rest
|
|
505
|
+
|
|
506
|
+
# Run tests
|
|
507
|
+
pytest
|
|
508
|
+
|
|
509
|
+
# Run all checks
|
|
510
|
+
black . && isort . && flake8 . && mypy src/libdyson_rest && pytest
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### Testing
|
|
514
|
+
|
|
515
|
+
Run tests with coverage:
|
|
516
|
+
|
|
517
|
+
```bash
|
|
518
|
+
# All tests
|
|
519
|
+
pytest
|
|
520
|
+
|
|
521
|
+
# Unit tests only
|
|
522
|
+
pytest tests/unit/
|
|
523
|
+
|
|
524
|
+
# Integration tests only
|
|
525
|
+
pytest tests/integration/
|
|
526
|
+
|
|
527
|
+
# With coverage report
|
|
528
|
+
pytest --cov=src/libdyson_rest --cov-report=html
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
## Project Structure
|
|
532
|
+
|
|
533
|
+
```
|
|
534
|
+
libdyson-rest/
|
|
535
|
+
├── src/
|
|
536
|
+
│ └── libdyson_rest/ # Main library code
|
|
537
|
+
│ ├── __init__.py
|
|
538
|
+
│ ├── client.py # Main API client
|
|
539
|
+
│ ├── exceptions.py # Custom exceptions
|
|
540
|
+
│ ├── models/ # Data models
|
|
541
|
+
│ └── utils/ # Utility functions
|
|
542
|
+
├── tests/
|
|
543
|
+
│ ├── unit/ # Unit tests
|
|
544
|
+
│ └── integration/ # Integration tests
|
|
545
|
+
├── .vscode/
|
|
546
|
+
│ └── tasks.json # VSCode tasks
|
|
547
|
+
├── requirements.txt # Production dependencies
|
|
548
|
+
├── requirements-dev.txt # Development dependencies
|
|
549
|
+
├── pyproject.toml # Project configuration
|
|
550
|
+
├── .flake8 # Flake8 configuration
|
|
551
|
+
├── .pre-commit-config.yaml # Pre-commit hooks
|
|
552
|
+
└── README.md
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
## Configuration Files
|
|
556
|
+
|
|
557
|
+
- **pyproject.toml**: Main project configuration (Black, isort, pytest, mypy)
|
|
558
|
+
- **.flake8**: Flake8 linting configuration
|
|
559
|
+
- **.pre-commit-config.yaml**: Git pre-commit hooks
|
|
560
|
+
- **requirements.txt**: Production dependencies
|
|
561
|
+
- **requirements-dev.txt**: Development dependencies
|
|
562
|
+
|
|
563
|
+
## Publishing to PyPI
|
|
564
|
+
|
|
565
|
+
This package is automatically published to PyPI using GitHub Actions. For detailed publishing instructions, see [PUBLISHING.md](PUBLISHING.md).
|
|
566
|
+
|
|
567
|
+
### Quick Publishing
|
|
568
|
+
|
|
569
|
+
- **Test Release**: GitHub Actions → Run workflow → TestPyPI
|
|
570
|
+
- **Production Release**: Create a GitHub release with version tag (e.g., `v0.2.0`)
|
|
571
|
+
- **Local Build**: `python .github/scripts/publish_to_pypi.py --check`
|
|
572
|
+
|
|
573
|
+
The package is available on PyPI as `libdyson-rest`.
|
|
574
|
+
|
|
575
|
+
## Contributing
|
|
576
|
+
|
|
577
|
+
1. Fork the repository
|
|
578
|
+
2. Create a feature branch: `git checkout -b feature-name`
|
|
579
|
+
3. Make your changes following the coding standards
|
|
580
|
+
4. Run all quality checks: ensure Black, Flake8, isort, MyPy, and tests pass
|
|
581
|
+
5. Commit your changes: `git commit -am 'Add feature'`
|
|
582
|
+
6. Push to the branch: `git push origin feature-name`
|
|
583
|
+
7. Create a Pull Request
|
|
584
|
+
|
|
585
|
+
All PRs must pass the full test suite and code quality checks.
|
|
586
|
+
|
|
587
|
+
## License
|
|
588
|
+
|
|
589
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
590
|
+
|
|
591
|
+
## Security
|
|
592
|
+
|
|
593
|
+
- No hardcoded credentials or sensitive data
|
|
594
|
+
- Use environment variables for configuration
|
|
595
|
+
- All user inputs are validated
|
|
596
|
+
- API responses are sanitized
|
|
597
|
+
|
|
598
|
+
## Roadmap
|
|
599
|
+
|
|
600
|
+
- [ ] Complete API endpoint coverage
|
|
601
|
+
- [ ] Asynchronous client support
|
|
602
|
+
- [ ] WebSocket real-time updates
|
|
603
|
+
- [ ] Command-line interface
|
|
604
|
+
- [ ] Docker container support
|