wyzeapy 0.5.28__py3-none-any.whl → 0.5.30__py3-none-any.whl

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.
wyzeapy/utils.py CHANGED
@@ -13,6 +13,10 @@ from Crypto.Cipher import AES
13
13
  from .exceptions import ParameterError, AccessTokenError, UnknownApiError
14
14
  from .types import ResponseCodes, PropertyIDs, Device, Event
15
15
 
16
+ """
17
+ Utility functions for encryption, decryption, error handling, and common Wyze API tasks.
18
+ """
19
+
16
20
  PADDING = bytes.fromhex("05")
17
21
 
18
22
 
@@ -44,7 +48,7 @@ def wyze_encrypt(key, text):
44
48
  cipher = AES.new(key, AES.MODE_CBC, iv)
45
49
  enc = cipher.encrypt(raw)
46
50
  b64_enc = base64.b64encode(enc).decode("ascii")
47
- b64_enc = b64_enc.replace("/", r'\/')
51
+ b64_enc = b64_enc.replace("/", r"\/")
48
52
  return b64_enc
49
53
 
50
54
 
@@ -57,7 +61,7 @@ def wyze_decrypt(key, enc):
57
61
  """
58
62
  enc = base64.b64decode(enc)
59
63
 
60
- key = key.encode('ascii')
64
+ key = key.encode("ascii")
61
65
  iv = key
62
66
  cipher = AES.new(key, AES.MODE_CBC, iv)
63
67
  decrypt = cipher.decrypt(enc)
@@ -68,44 +72,79 @@ def wyze_decrypt(key, enc):
68
72
 
69
73
 
70
74
  def wyze_decrypt_cbc(key: str, enc_hex_str: str) -> str:
75
+ """
76
+ Decrypt a hex-encoded string using Wyze's CBC decryption with MD5 based key.
77
+
78
+ Args:
79
+ key: The secret key string.
80
+ enc_hex_str: The encrypted data as a hex string.
81
+
82
+ Returns:
83
+ The decrypted plaintext string.
84
+ """
71
85
  key_hash = hashlib.md5(key.encode("utf-8")).digest()
72
-
86
+
73
87
  iv = b"0123456789ABCDEF"
74
88
  cipher = AES.new(key_hash, AES.MODE_CBC, iv)
75
-
89
+
76
90
  encrypted_bytes = binascii.unhexlify(enc_hex_str)
77
91
  decrypted_bytes = cipher.decrypt(encrypted_bytes)
78
-
92
+
79
93
  # PKCS5Padding
80
94
  padding_length = decrypted_bytes[-1]
81
95
  return decrypted_bytes[:-padding_length].decode()
82
96
 
83
97
 
84
98
  def create_password(password: str) -> str:
99
+ """
100
+ Derive the Wyze API password hash using a triple MD5 hashing scheme.
101
+
102
+ Args:
103
+ password: The plain user password string.
104
+
105
+ Returns:
106
+ The hashed password as a hex string.
107
+ """
85
108
  hex1 = hashlib.md5(password.encode()).hexdigest()
86
109
  hex2 = hashlib.md5(hex1.encode()).hexdigest()
87
110
  return hashlib.md5(hex2.encode()).hexdigest()
88
111
 
89
112
 
90
113
  def check_for_errors_standard(service, response_json: Dict[str, Any]) -> None:
91
- response_code = response_json['code']
114
+ """
115
+ Check for standard Wyze API error codes and raise exceptions as needed.
116
+
117
+ Args:
118
+ service: The service instance triggering the call.
119
+ response_json: The JSON response from the API.
120
+ """
121
+ response_code = response_json["code"]
92
122
  if response_code != ResponseCodes.SUCCESS.value:
93
123
  if response_code == ResponseCodes.PARAMETER_ERROR.value:
94
- raise ParameterError(response_code, response_json['msg'])
124
+ raise ParameterError(response_code, response_json["msg"])
95
125
  elif response_code == ResponseCodes.ACCESS_TOKEN_ERROR.value:
96
126
  service._auth_lib.token.expired = True
97
- raise AccessTokenError(response_code, "Access Token expired, attempting to refresh")
127
+ raise AccessTokenError(
128
+ response_code, "Access Token expired, attempting to refresh"
129
+ )
98
130
  elif response_code == ResponseCodes.DEVICE_OFFLINE.value:
99
131
  return
100
132
  else:
101
- raise UnknownApiError(response_code, response_json['msg'])
133
+ raise UnknownApiError(response_code, response_json["msg"])
102
134
 
103
135
 
104
136
  def check_for_errors_lock(service, response_json: Dict[str, Any]) -> None:
105
- if response_json['ErrNo'] != 0:
106
- if response_json.get('code') == ResponseCodes.PARAMETER_ERROR.value:
137
+ """
138
+ Check for lock-specific API errors and raise exceptions as needed.
139
+
140
+ Args:
141
+ service: The lock service instance.
142
+ response_json: The JSON response from the lock API.
143
+ """
144
+ if response_json["ErrNo"] != 0:
145
+ if response_json.get("code") == ResponseCodes.PARAMETER_ERROR.value:
107
146
  raise ParameterError(response_json)
108
- elif response_json.get('code') == ResponseCodes.ACCESS_TOKEN_ERROR.value:
147
+ elif response_json.get("code") == ResponseCodes.ACCESS_TOKEN_ERROR.value:
109
148
  service._auth_lib.token.expired = True
110
149
  raise AccessTokenError("Access Token expired, attempting to refresh")
111
150
  else:
@@ -113,8 +152,15 @@ def check_for_errors_lock(service, response_json: Dict[str, Any]) -> None:
113
152
 
114
153
 
115
154
  def check_for_errors_devicemgmt(service, response_json: Dict[Any, Any]) -> None:
116
- if response_json['status'] != 200:
117
- if "InvalidTokenError>" in response_json['response']['errors'][0]['message']:
155
+ """
156
+ Check for device management API errors and raise exceptions as needed.
157
+
158
+ Args:
159
+ service: The device management service instance.
160
+ response_json: The JSON response from the device management API.
161
+ """
162
+ if response_json["status"] != 200:
163
+ if "InvalidTokenError>" in response_json["response"]["errors"][0]["message"]:
118
164
  service._auth_lib.token.expired = True
119
165
  raise AccessTokenError("Access Token expired, attempting to refresh")
120
166
  else:
@@ -122,20 +168,45 @@ def check_for_errors_devicemgmt(service, response_json: Dict[Any, Any]) -> None:
122
168
 
123
169
 
124
170
  def check_for_errors_iot(service, response_json: Dict[Any, Any]) -> None:
125
- if response_json['code'] != 1:
126
- if str(response_json['code']) == ResponseCodes.ACCESS_TOKEN_ERROR.value:
171
+ """
172
+ Check for IoT API errors and raise exceptions as needed.
173
+
174
+ Args:
175
+ service: The IoT service instance.
176
+ response_json: The JSON response from the IoT API.
177
+ """
178
+ if response_json["code"] != 1:
179
+ if str(response_json["code"]) == ResponseCodes.ACCESS_TOKEN_ERROR.value:
127
180
  service._auth_lib.token.expired = True
128
181
  raise AccessTokenError("Access Token expired, attempting to refresh")
129
182
  else:
130
183
  raise UnknownApiError(response_json)
131
184
 
185
+
132
186
  def check_for_errors_hms(service, response_json: Dict[Any, Any]) -> None:
133
- if response_json['message'] is None:
187
+ """
188
+ Check for home monitoring system (HMS) API errors and raise exceptions as needed.
189
+
190
+ Args:
191
+ service: The HMS service instance.
192
+ response_json: The JSON response from the HMS API.
193
+ """
194
+ if response_json["message"] is None:
134
195
  service._auth_lib.token.expired = True
135
196
  raise AccessTokenError("Access Token expired, attempting to refresh")
136
197
 
137
198
 
138
199
  def return_event_for_device(device: Device, events: List[Event]) -> Optional[Event]:
200
+ """
201
+ Retrieve the most recent event matching a given device from a list of events.
202
+
203
+ Args:
204
+ device: The device to match against event.device_mac.
205
+ events: List of events to search.
206
+
207
+ Returns:
208
+ The first matching Event or None if no match is found.
209
+ """
139
210
  for event in events:
140
211
  if event.device_mac == device.mac:
141
212
  return event
@@ -144,4 +215,14 @@ def return_event_for_device(device: Device, events: List[Event]) -> Optional[Eve
144
215
 
145
216
 
146
217
  def create_pid_pair(pid_enum: PropertyIDs, value: str) -> Dict[str, str]:
218
+ """
219
+ Create a property ID/value pair dictionary for API payloads.
220
+
221
+ Args:
222
+ pid_enum: PropertyIDs enum member for the property.
223
+ value: The value to set for the property.
224
+
225
+ Returns:
226
+ A dict with 'pid' and 'pvalue' keys for the Wyze API.
227
+ """
147
228
  return {"pid": pid_enum.value, "pvalue": value}
wyzeapy/wyze_auth_lib.py CHANGED
@@ -10,7 +10,17 @@ from typing import Dict, Any, Optional
10
10
 
11
11
  from aiohttp import TCPConnector, ClientSession, ContentTypeError
12
12
 
13
- from .const import API_KEY, PHONE_ID, APP_NAME, APP_VERSION, SC, SV, PHONE_SYSTEM_TYPE, APP_VER, APP_INFO
13
+ from .const import (
14
+ API_KEY,
15
+ PHONE_ID,
16
+ APP_NAME,
17
+ APP_VERSION,
18
+ SC,
19
+ SV,
20
+ PHONE_SYSTEM_TYPE,
21
+ APP_VER,
22
+ APP_INFO,
23
+ )
14
24
  from .exceptions import (
15
25
  UnknownApiError,
16
26
  TwoFactorAuthenticationEnabled,
@@ -19,10 +29,28 @@ from .exceptions import (
19
29
  from .utils import create_password, check_for_errors_standard
20
30
 
21
31
  _LOGGER = logging.getLogger(__name__)
32
+ """
33
+ Authentication token data and timing management.
34
+
35
+ This module handles Wyze API authentication tokens, including expiration
36
+ tracking, automatic refresh timing, and secure request methods in WyzeAuthLib.
37
+ """
22
38
 
23
39
 
24
40
  class Token:
25
- # Token is apparently good for 24 hours, so refresh after 23
41
+ """Represents Wyze API access/refresh token and expiration tracking.
42
+
43
+ Attributes:
44
+ _access_token: Current access token string.
45
+ _refresh_token: Current refresh token string.
46
+ expired: Flag indicating if the token is marked expired.
47
+ _refresh_time: Unix timestamp when token should be refreshed.
48
+
49
+ Class Attributes:
50
+ REFRESH_INTERVAL: Time in seconds before token auto-refresh (23h).
51
+ """
52
+
53
+ # Token is good for 24 hours; schedule refresh after 23 hours
26
54
  REFRESH_INTERVAL = 82800
27
55
 
28
56
  def __init__(self, access_token, refresh_token, refresh_time: float = None):
@@ -79,6 +107,16 @@ class WyzeAuthLib:
79
107
  token: Optional[Token] = None,
80
108
  token_callback=None,
81
109
  ):
110
+ """Initialize WyzeAuthLib for authentication and token management.
111
+
112
+ Args:
113
+ username: Wyze account email address.
114
+ password: Plaintext or hashed account password.
115
+ key_id: Third-party API key ID for Wyze credentials.
116
+ api_key: Third-party API key for Wyze credentials.
117
+ token: Existing Token instance for reuse (optional).
118
+ token_callback: Callback to invoke on token updates.
119
+ """
82
120
  self._username = username
83
121
  self._password = password
84
122
  self._key_id = key_id
@@ -100,6 +138,22 @@ class WyzeAuthLib:
100
138
  token: Optional[Token] = None,
101
139
  token_callback=None,
102
140
  ):
141
+ """Factory to instantiate WyzeAuthLib with credentials or existing token.
142
+
143
+ Args:
144
+ username: Wyze account email (optional if token provided).
145
+ password: Wyze account password (optional if token provided).
146
+ key_id: Third-party API key ID (required for login).
147
+ api_key: Third-party API key (required for login).
148
+ token: Existing Token instance (skip login flow).
149
+ token_callback: Callback for token refresh events.
150
+
151
+ Returns:
152
+ A configured WyzeAuthLib instance.
153
+
154
+ Raises:
155
+ AttributeError: When neither credentials nor token are provided.
156
+ """
103
157
  self = cls(
104
158
  username=username,
105
159
  password=password,
@@ -111,7 +165,11 @@ class WyzeAuthLib:
111
165
 
112
166
  if self._username is None and self._password is None and self.token is None:
113
167
  raise AttributeError("Must provide a username, password or token")
114
- elif self.token is None and self._username is not None and self._password is not None:
168
+ elif (
169
+ self.token is None
170
+ and self._username is not None
171
+ and self._password is not None
172
+ ):
115
173
  assert self._username != ""
116
174
  assert self._password != ""
117
175
 
@@ -120,6 +178,22 @@ class WyzeAuthLib:
120
178
  async def get_token_with_username_password(
121
179
  self, username, password, key_id, api_key
122
180
  ) -> Token:
181
+ """Authenticate using email/password and retrieve new Token.
182
+
183
+ Args:
184
+ username: Wyze account email.
185
+ password: Plaintext Wyze account password.
186
+ key_id: Third-party API key ID.
187
+ api_key: Third-party API key.
188
+
189
+ Returns:
190
+ A new Token instance with access and refresh tokens.
191
+
192
+ Raises:
193
+ TwoFactorAuthenticationEnabled: When 2FA is required.
194
+ AccessTokenError: On invalid credentials.
195
+ UnknownApiError: For other authentication errors.
196
+ """
123
197
  self._username = username
124
198
  self._password = create_password(password)
125
199
  self._key_id = key_id
@@ -138,42 +212,57 @@ class WyzeAuthLib:
138
212
  json=login_payload,
139
213
  )
140
214
 
141
- if response_json.get('errorCode') is not None:
215
+ if response_json.get("errorCode") is not None:
142
216
  _LOGGER.error(f"Unable to login with response from Wyze: {response_json}")
143
217
  if response_json["errorCode"] == 1000:
144
218
  raise AccessTokenError
145
219
  raise UnknownApiError(response_json)
146
220
 
147
- if response_json.get('mfa_options') is not None:
221
+ if response_json.get("mfa_options") is not None:
148
222
  # Store the TOTP verification setting in the token and raise exception
149
223
  if "TotpVerificationCode" in response_json.get("mfa_options"):
150
224
  self.two_factor_type = "TOTP"
151
225
  # Store the verification_id from the response, it's needed for the 2fa payload.
152
- self.verification_id = response_json["mfa_details"]["totp_apps"][0]["app_id"]
226
+ self.verification_id = response_json["mfa_details"]["totp_apps"][0][
227
+ "app_id"
228
+ ]
153
229
  raise TwoFactorAuthenticationEnabled
154
230
  # 2fa using SMS, store sms as 2fa method in token, send the code then raise exception
155
231
  if "PrimaryPhone" in response_json.get("mfa_options"):
156
232
  self.two_factor_type = "SMS"
157
233
  params = {
158
- 'mfaPhoneType': 'Primary',
159
- 'sessionId': response_json.get("sms_session_id"),
160
- 'userId': response_json['user_id'],
234
+ "mfaPhoneType": "Primary",
235
+ "sessionId": response_json.get("sms_session_id"),
236
+ "userId": response_json["user_id"],
161
237
  }
162
- response_json = await self.post('https://auth-prod.api.wyze.com/user/login/sendSmsCode',
163
- headers=headers, data=params)
238
+ response_json = await self.post(
239
+ "https://auth-prod.api.wyze.com/user/login/sendSmsCode",
240
+ headers=headers,
241
+ data=params,
242
+ )
164
243
  # Store the session_id from this response, it's needed for the 2fa payload.
165
- self.session_id = response_json['session_id']
244
+ self.session_id = response_json["session_id"]
166
245
  raise TwoFactorAuthenticationEnabled
167
246
 
168
- self.token = Token(response_json['access_token'], response_json['refresh_token'])
247
+ self.token = Token(
248
+ response_json["access_token"], response_json["refresh_token"]
249
+ )
169
250
  await self.token_callback(self.token)
170
251
  return self.token
171
252
 
172
253
  async def get_token_with_2fa(self, verification_code) -> Token:
254
+ """Complete login flow using two-factor authentication code.
255
+
256
+ Args:
257
+ verification_code: 6-digit TOTP or SMS code for 2FA.
258
+
259
+ Returns:
260
+ A new Token instance after successful 2FA verification.
261
+ """
173
262
  headers = {
174
- 'Phone-Id': PHONE_ID,
175
- 'User-Agent': APP_INFO,
176
- 'X-API-Key': API_KEY,
263
+ "Phone-Id": PHONE_ID,
264
+ "User-Agent": APP_INFO,
265
+ "X-API-Key": API_KEY,
177
266
  }
178
267
  # TOTP Payload
179
268
  if self.two_factor_type == "TOTP":
@@ -182,7 +271,7 @@ class WyzeAuthLib:
182
271
  "password": self._password,
183
272
  "mfa_type": "TotpVerificationCode",
184
273
  "verification_id": self.verification_id,
185
- "verification_code": verification_code
274
+ "verification_code": verification_code,
186
275
  }
187
276
  # SMS Payload
188
277
  else:
@@ -191,22 +280,26 @@ class WyzeAuthLib:
191
280
  "password": self._password,
192
281
  "mfa_type": "PrimaryPhone",
193
282
  "verification_id": self.session_id,
194
- "verification_code": verification_code
283
+ "verification_code": verification_code,
195
284
  }
196
285
 
197
286
  response_json = await self.post(
198
- 'https://auth-prod.api.wyze.com/user/login',
199
- headers=headers, json=payload)
287
+ "https://auth-prod.api.wyze.com/user/login", headers=headers, json=payload
288
+ )
200
289
 
201
- self.token = Token(response_json['access_token'], response_json['refresh_token'])
290
+ self.token = Token(
291
+ response_json["access_token"], response_json["refresh_token"]
292
+ )
202
293
  await self.token_callback(self.token)
203
294
  return self.token
204
295
 
205
296
  @property
206
297
  def should_refresh(self) -> bool:
298
+ """Check whether the current token has reached its refresh time."""
207
299
  return time.time() >= self.token.refresh_time
208
300
 
209
301
  async def refresh_if_should(self):
302
+ """Refresh the token proactively if expired or past refresh_time."""
210
303
  if self.should_refresh or self.token.expired:
211
304
  async with self.refresh_lock:
212
305
  if self.should_refresh or self.token.expired:
@@ -214,6 +307,12 @@ class WyzeAuthLib:
214
307
  await self.refresh()
215
308
 
216
309
  async def refresh(self) -> None:
310
+ """Exchange the refresh token for a new access token and update internal Token.
311
+
312
+ Raises:
313
+ AccessTokenError: If refresh fails due to invalid refresh token.
314
+ UnknownApiError: For other errors during refresh.
315
+ """
217
316
  payload = {
218
317
  "phone_id": PHONE_ID,
219
318
  "app_name": APP_NAME,
@@ -223,25 +322,33 @@ class WyzeAuthLib:
223
322
  "phone_system_type": PHONE_SYSTEM_TYPE,
224
323
  "app_ver": APP_VER,
225
324
  "ts": int(time.time()),
226
- "refresh_token": self.token.refresh_token
325
+ "refresh_token": self.token.refresh_token,
227
326
  }
228
327
 
229
- headers = {
230
- "X-API-Key": API_KEY
231
- }
328
+ headers = {"X-API-Key": API_KEY}
232
329
 
233
- async with ClientSession(connector=TCPConnector(ttl_dns_cache=(30 * 60))) as _session:
234
- response = await _session.post("https://api.wyzecam.com/app/user/refresh_token", headers=headers,
235
- json=payload)
330
+ async with ClientSession(
331
+ connector=TCPConnector(ttl_dns_cache=(30 * 60))
332
+ ) as _session:
333
+ response = await _session.post(
334
+ "https://api.wyzecam.com/app/user/refresh_token",
335
+ headers=headers,
336
+ json=payload,
337
+ )
236
338
  response_json = await response.json()
237
339
  check_for_errors_standard(self, response_json)
238
340
 
239
- self.token.access_token = response_json['data']['access_token']
240
- self.token.refresh_token = response_json['data']['refresh_token']
341
+ self.token.access_token = response_json["data"]["access_token"]
342
+ self.token.refresh_token = response_json["data"]["refresh_token"]
241
343
  await self.token_callback(self.token)
242
344
  self.token.expired = False
243
345
 
244
346
  def sanitize(self, data):
347
+ """Recursively sanitize sensitive fields in dicts for safe logging.
348
+
349
+ Args:
350
+ data: The dict to sanitize; returned sanitized copy.
351
+ """
245
352
  if data and type(data) is dict:
246
353
  # value is unused, but it prevents us from having to split the tuple to check against SANITIZE_FIELDS
247
354
  for key, value in data.items():
@@ -252,7 +359,20 @@ class WyzeAuthLib:
252
359
  return data
253
360
 
254
361
  async def post(self, url, json=None, headers=None, data=None) -> Dict[Any, Any]:
255
- async with ClientSession(connector=TCPConnector(ttl_dns_cache=(30 * 60))) as _session:
362
+ """Send an HTTP POST request with sanitized logging.
363
+
364
+ Args:
365
+ url: Request URL.
366
+ json: Optional JSON payload.
367
+ headers: Optional headers.
368
+ data: Optional form data.
369
+
370
+ Returns:
371
+ Parsed JSON response.
372
+ """
373
+ async with ClientSession(
374
+ connector=TCPConnector(ttl_dns_cache=(30 * 60))
375
+ ) as _session:
256
376
  response = await _session.post(url, json=json, headers=headers, data=data)
257
377
  # Relocated these below as the sanitization seems to modify the data before it goes to the post.
258
378
  _LOGGER.debug("Request:")
@@ -267,9 +387,15 @@ class WyzeAuthLib:
267
387
  except ContentTypeError:
268
388
  _LOGGER.debug(f"Response: {response}")
269
389
  return await response.json()
270
-
390
+
271
391
  async def put(self, url, json=None, headers=None, data=None) -> Dict[Any, Any]:
272
- async with ClientSession(connector=TCPConnector(ttl_dns_cache=(30 * 60))) as _session:
392
+ """Send an HTTP PUT request with sanitized logging.
393
+
394
+ See `post` for parameter details.
395
+ """
396
+ async with ClientSession(
397
+ connector=TCPConnector(ttl_dns_cache=(30 * 60))
398
+ ) as _session:
273
399
  response = await _session.put(url, json=json, headers=headers, data=data)
274
400
  # Relocated these below as the sanitization seems to modify the data before it goes to the post.
275
401
  _LOGGER.debug("Request:")
@@ -286,7 +412,19 @@ class WyzeAuthLib:
286
412
  return await response.json()
287
413
 
288
414
  async def get(self, url, headers=None, params=None) -> Dict[Any, Any]:
289
- async with ClientSession(connector=TCPConnector(ttl_dns_cache=(30 * 60))) as _session:
415
+ """Send an HTTP GET request with sanitized logging.
416
+
417
+ Args:
418
+ url: Request URL.
419
+ headers: Optional headers.
420
+ params: Optional query parameters.
421
+
422
+ Returns:
423
+ Parsed JSON response.
424
+ """
425
+ async with ClientSession(
426
+ connector=TCPConnector(ttl_dns_cache=(30 * 60))
427
+ ) as _session:
290
428
  response = await _session.get(url, params=params, headers=headers)
291
429
  # Relocated these below as the sanitization seems to modify the data before it goes to the post.
292
430
  _LOGGER.debug("Request:")
@@ -302,8 +440,16 @@ class WyzeAuthLib:
302
440
  return await response.json()
303
441
 
304
442
  async def patch(self, url, headers=None, params=None, json=None) -> Dict[Any, Any]:
305
- async with ClientSession(connector=TCPConnector(ttl_dns_cache=(30 * 60))) as _session:
306
- response = await _session.patch(url, headers=headers, params=params, json=json)
443
+ """Send an HTTP PATCH request with sanitized logging.
444
+
445
+ See `get`/`post` for parameter details.
446
+ """
447
+ async with ClientSession(
448
+ connector=TCPConnector(ttl_dns_cache=(30 * 60))
449
+ ) as _session:
450
+ response = await _session.patch(
451
+ url, headers=headers, params=params, json=json
452
+ )
307
453
  # Relocated these below as the sanitization seems to modify the data before it goes to the post.
308
454
  _LOGGER.debug("Request:")
309
455
  _LOGGER.debug(f"url: {url}")
@@ -319,7 +465,19 @@ class WyzeAuthLib:
319
465
  return await response.json()
320
466
 
321
467
  async def delete(self, url, headers=None, json=None) -> Dict[Any, Any]:
322
- async with ClientSession(connector=TCPConnector(ttl_dns_cache=(30 * 60))) as _session:
468
+ """Send an HTTP DELETE request with sanitized logging.
469
+
470
+ Args:
471
+ url: Request URL.
472
+ headers: Optional headers.
473
+ json: Optional JSON payload.
474
+
475
+ Returns:
476
+ Parsed JSON response.
477
+ """
478
+ async with ClientSession(
479
+ connector=TCPConnector(ttl_dns_cache=(30 * 60))
480
+ ) as _session:
323
481
  response = await _session.delete(url, headers=headers, json=json)
324
482
  # Relocated these below as the sanitization seems to modify the data before it goes to the post.
325
483
  _LOGGER.debug("Request:")
@@ -0,0 +1,13 @@
1
+ Metadata-Version: 2.4
2
+ Name: wyzeapy
3
+ Version: 0.5.30
4
+ Summary: A library for interacting with Wyze devices
5
+ Author-email: Katie Mulliken <katie@mulliken.net>
6
+ License: GPL-3.0-only
7
+ Requires-Python: >=3.11.0
8
+ Requires-Dist: aiodns<4.0.0,>=3.2.0
9
+ Requires-Dist: aiohttp<4.0.0,>=3.11.12
10
+ Requires-Dist: pycryptodome<4.0.0,>=3.21.0
11
+ Provides-Extra: dev
12
+ Requires-Dist: pdoc<16.0.0,>=15.0.3; extra == 'dev'
13
+ Requires-Dist: pytest<9.0.0,>=7.0.0; extra == 'dev'
@@ -0,0 +1,24 @@
1
+ wyzeapy/__init__.py,sha256=QckgsDmJxrQFPdVT4rheUqi3cCBSN-jkEWD-6hHmrqk,17248
2
+ wyzeapy/const.py,sha256=3PV2Uq7wDD1X0ZmJBg8GWXYGHrpJvszyr9FuvwjHyus,1249
3
+ wyzeapy/crypto.py,sha256=ApzPL0hrrd0D4k2jB5psNatJvUSzx1Kxui6_l4NJGO8,2057
4
+ wyzeapy/exceptions.py,sha256=uKVWooofK22DZ3o9kwxnlJXnhk0VMJXnp-26pNavAis,1136
5
+ wyzeapy/payload_factory.py,sha256=D37H0rTFakqnhmYrYjBAhFvRSquujINA7b8UyhRUjl0,20547
6
+ wyzeapy/types.py,sha256=LPLc86FkEy8rUoPMwG8xB73NCTVRkJ1O5w7uSfDydLQ,6785
7
+ wyzeapy/utils.py,sha256=EXnsZFBxgI3LsIh9Ttg4gq3Aq1VMLOxBUPCspWJX9IQ,7407
8
+ wyzeapy/wyze_auth_lib.py,sha256=CiWdl_UAzYxKLS8yCfjXxEI87gStJede2eS4g7KlnjE,18273
9
+ wyzeapy/services/__init__.py,sha256=hbdyglbWQjM4XlNqPIACOEbspdsEEm4k5VXZ1hI0gc8,77
10
+ wyzeapy/services/base_service.py,sha256=HUIU2uugHhY0KiGvc5ALYQn4NnsFMNZzECLJgqPHR9Q,34573
11
+ wyzeapy/services/bulb_service.py,sha256=DNuT9PBmFhXpaD9rcjoYZMt-TLWWEAC3o0Yyvw_itHA,7825
12
+ wyzeapy/services/camera_service.py,sha256=vaJIChKDMg3zWcoC_JqITDBjw4sgFdyUEkl3_w2ML8I,11170
13
+ wyzeapy/services/hms_service.py,sha256=lQojRASz9AlwqkRfj7W7gOKXpLHrHHVwBGMw5WJ23Nc,2450
14
+ wyzeapy/services/irrigation_service.py,sha256=lzQoUT2COlIMF1oTTDsvb-7H7zkra96i_Cxr4zkfYzs,7110
15
+ wyzeapy/services/lock_service.py,sha256=NBjlr7pL5zJqdJaH33v1i6BbLHb7TXCkoCql4hCr8J8,2234
16
+ wyzeapy/services/sensor_service.py,sha256=WSNz0OOLoZKru4d1ZwZ80-pdJ321HssUnwEgVfwX2zM,3578
17
+ wyzeapy/services/switch_service.py,sha256=2O3J8-hP3vOgGVi0cKiKG_3j71zI6rHiqQd3u7CEKcE,2244
18
+ wyzeapy/services/thermostat_service.py,sha256=_d-UbD65JArhwsslawvwpTmfVC4tMksY-L1Uu7HW0m4,5360
19
+ wyzeapy/services/update_manager.py,sha256=5pZJmnyN4rlYJwMtEY13NlPBssnRLhtlLLmXr9t990Q,6770
20
+ wyzeapy/services/wall_switch_service.py,sha256=cBKmnB2InHKIuoPwQ47t1rDtDplyOyGQYvnfX4fXFcc,4339
21
+ wyzeapy/tests/test_irrigation_service.py,sha256=6rknfL0FzkptHs4CcYBamBTMD4pHcG0RyYNe3OgYBI8,21270
22
+ wyzeapy-0.5.30.dist-info/METADATA,sha256=7OVSrLycTNIfh3zcmTI-38EZ0xt4NZExNKamzuTT61I,445
23
+ wyzeapy-0.5.30.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
24
+ wyzeapy-0.5.30.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.1.1
2
+ Generator: hatchling 1.27.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any