Skip to content

Commit 32e084e

Browse files
committed
Add flake8 and improve formatting to pass all checks
1 parent bf0ce65 commit 32e084e

19 files changed

+336
-218
lines changed

.flake8

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[flake8]
2+
max-line-length = 120
3+
extend-ignore = E203

fmd_api/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@
1212
"DeviceNotFoundError",
1313
"OperationError",
1414
"__version__",
15-
]
15+
]

fmd_api/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "2.0.0-dev6"
1+
__version__ = "2.0.0-dev7"

fmd_api/client.py

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,8 @@
2626
from cryptography.hazmat.primitives.asymmetric import padding
2727
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
2828

29-
from .helpers import b64_decode_padded, _pad_base64
30-
from .exceptions import FmdApiException, AuthenticationError
31-
from .models import PhotoResult, Location
29+
from .helpers import _pad_base64
30+
from .exceptions import FmdApiException
3231

3332
# Constants copied from original module to ensure parity
3433
CONTEXT_STRING_LOGIN = "context:loginAuthentication"
@@ -183,12 +182,18 @@ async def _make_api_request(self, method: str, endpoint: str, payload: Any,
183182
# Handle 401 -> re-authenticate once
184183
if resp.status == 401 and retry_auth and self._fmd_id and self._password:
185184
log.info("Received 401 Unauthorized, re-authenticating...")
186-
await self.authenticate(self._fmd_id, self._password, self.session_duration)
185+
await self.authenticate(
186+
self._fmd_id, self._password, self.session_duration)
187187
payload["IDT"] = self.access_token
188-
return await self._make_api_request(method, endpoint, payload, stream, expect_json, retry_auth=False)
188+
return await self._make_api_request(
189+
method, endpoint, payload, stream, expect_json,
190+
retry_auth=False)
189191

190192
resp.raise_for_status()
191-
log.debug(f"{endpoint} response - status: {resp.status}, content-type: {resp.content_type}, content-length: {resp.content_length}")
193+
log.debug(
194+
f"{endpoint} response - status: {resp.status}, "
195+
f"content-type: {resp.content_type}, "
196+
f"content-length: {resp.content_length}")
192197

193198
if not stream:
194199
if expect_json:
@@ -223,13 +228,19 @@ async def _make_api_request(self, method: str, endpoint: str, payload: Any,
223228
# -------------------------
224229
# Location / picture access
225230
# -------------------------
226-
async def get_locations(self, num_to_get: int = -1, skip_empty: bool = True, max_attempts: int = 10) -> List[str]:
231+
async def get_locations(
232+
self, num_to_get: int = -1, skip_empty: bool = True,
233+
max_attempts: int = 10) -> List[str]:
227234
"""
228235
Fetches all or the N most recent location blobs.
229236
Returns list of base64-encoded blobs (strings), same as original get_all_locations.
230237
"""
231-
log.debug(f"Getting locations, num_to_get={num_to_get}, skip_empty={skip_empty}")
232-
size_str = await self._make_api_request("PUT", "/api/v1/locationDataSize", {"IDT": self.access_token, "Data": ""})
238+
log.debug(
239+
f"Getting locations, num_to_get={num_to_get}, "
240+
f"skip_empty={skip_empty}")
241+
size_str = await self._make_api_request(
242+
"PUT", "/api/v1/locationDataSize",
243+
{"IDT": self.access_token, "Data": ""})
233244
size = int(size_str)
234245
log.debug(f"Server reports {size} locations available")
235246
if size == 0:
@@ -242,7 +253,9 @@ async def get_locations(self, num_to_get: int = -1, skip_empty: bool = True, max
242253
indices = range(size)
243254
for i in indices:
244255
log.info(f" - Downloading location at index {i}...")
245-
blob = await self._make_api_request("PUT", "/api/v1/location", {"IDT": self.access_token, "Data": str(i)})
256+
blob = await self._make_api_request(
257+
"PUT", "/api/v1/location",
258+
{"IDT": self.access_token, "Data": str(i)})
246259
locations.append(blob)
247260
return locations
248261
else:
@@ -251,8 +264,11 @@ async def get_locations(self, num_to_get: int = -1, skip_empty: bool = True, max
251264
start_index = size - 1
252265

253266
if skip_empty:
254-
indices = range(start_index, max(-1, start_index - max_attempts), -1)
255-
log.info(f"Will search for {num_to_download} non-empty location(s) starting from index {start_index}")
267+
indices = range(
268+
start_index, max(-1, start_index - max_attempts), -1)
269+
log.info(
270+
f"Will search for {num_to_download} non-empty location(s) "
271+
f"starting from index {start_index}")
256272
else:
257273
end_index = size - num_to_download
258274
indices = range(start_index, end_index - 1, -1)
@@ -272,15 +288,19 @@ async def get_locations(self, num_to_get: int = -1, skip_empty: bool = True, max
272288
log.warning(f"Empty blob received for location index {i}, repr: {repr(blob[:50] if blob else blob)}")
273289

274290
if not locations and num_to_get != -1:
275-
log.warning(f"No valid locations found after checking {min(max_attempts, size)} indices")
291+
log.warning(
292+
f"No valid locations found after checking "
293+
f"{min(max_attempts, size)} indices")
276294

277295
return locations
278296

279297
async def get_pictures(self, num_to_get: int = -1) -> List[Any]:
280298
"""Fetches all or the N most recent picture metadata blobs (raw server response)."""
281299
try:
282300
await self._ensure_session()
283-
async with self._session.put(f"{self.base_url}/api/v1/pictures", json={"IDT": self.access_token, "Data": ""}) as resp:
301+
async with self._session.put(
302+
f"{self.base_url}/api/v1/pictures",
303+
json={"IDT": self.access_token, "Data": ""}) as resp:
284304
resp.raise_for_status()
285305
json_data = await resp.json()
286306
# Extract the Data field if it exists, otherwise use the response as-is
@@ -377,8 +397,6 @@ async def request_location(self, provider: str = "all") -> bool:
377397
log.info(f"Requesting location update with provider: {provider} (command: {command})")
378398
return await self.send_command(command)
379399

380-
381-
382400
async def set_bluetooth(self, enable: bool) -> bool:
383401
"""Set Bluetooth power explicitly: True = on, False = off."""
384402
command = "bluetooth on" if enable else "bluetooth off"
@@ -414,4 +432,4 @@ async def take_picture(self, camera: str = "back") -> bool:
414432
raise ValueError(f"Invalid camera '{camera}'. Must be 'front' or 'back'")
415433
command = "camera front" if camera == "front" else "camera back"
416434
log.info(f"Requesting picture from {camera} camera")
417-
return await self.send_command(command)
435+
return await self.send_command(command)

fmd_api/device.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@
1414
from .helpers import b64_decode_padded
1515
from .client import FmdClient
1616

17+
1718
def _parse_location_blob(blob_b64: str) -> Location:
1819
"""Helper to decrypt and parse a location blob into Location dataclass."""
1920
# This function expects the caller to pass in a client to decrypt; kept here
2021
# for signature clarity in Device methods.
2122
raise RuntimeError("Internal: _parse_location_blob should not be called directly")
2223

24+
2325
class Device:
2426
def __init__(self, client: FmdClient, fmd_id: str, raw: dict = None):
2527
self.client = client
@@ -128,16 +130,22 @@ async def download_photo(self, picture_blob_b64: str) -> PhotoResult:
128130
except Exception:
129131
raw_meta = {"note": "binary image or base64 string; no JSON metadata"}
130132
# Build PhotoResult; mime type not provided by server so default to image/jpeg
131-
return PhotoResult(data=image_bytes, mime_type="image/jpeg", timestamp=datetime.now(timezone.utc), raw=raw_meta)
133+
return PhotoResult(
134+
data=image_bytes,
135+
mime_type="image/jpeg",
136+
timestamp=datetime.now(
137+
timezone.utc),
138+
raw=raw_meta)
132139
except Exception as e:
133140
raise OperationError(f"Failed to decode picture blob: {e}") from e
134141

135142
async def lock(self, message: Optional[str] = None, passcode: Optional[str] = None) -> bool:
136143
# The original API supports "lock" command; it does not carry message/passcode in the current client
137-
# Implementation preserves original behavior (sends "lock" command). Extensions can append data if server supports it.
144+
# Implementation preserves original behavior (sends "lock" command).
145+
# Extensions can append data if server supports it.
138146
return await self.client.send_command("lock")
139147

140148
async def wipe(self, confirm: bool = False) -> bool:
141149
if not confirm:
142150
raise OperationError("wipe() requires confirm=True to proceed (destructive action)")
143-
return await self.client.send_command("delete")
151+
return await self.client.send_command("delete")

fmd_api/exceptions.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,26 @@
11
"""Typed exceptions for fmd_api v2."""
2+
3+
24
class FmdApiException(Exception):
35
"""Base exception for FMD API errors."""
46
pass
57

8+
69
class AuthenticationError(FmdApiException):
710
"""Raised when authentication fails."""
811
pass
912

13+
1014
class DeviceNotFoundError(FmdApiException):
1115
"""Raised when a requested device cannot be found."""
1216
pass
1317

18+
1419
class RateLimitError(FmdApiException):
1520
"""Raised when the server indicates rate limiting."""
1621
pass
1722

23+
1824
class OperationError(FmdApiException):
1925
"""Raised for failed operations (commands, downloads, etc)."""
20-
pass
26+
pass

fmd_api/helpers.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
"""Small helper utilities."""
22
import base64
3-
from typing import Optional
3+
44

55
def _pad_base64(s: str) -> str:
66
return s + '=' * (-len(s) % 4)
77

8+
89
def b64_decode_padded(s: str) -> bytes:
910
return base64.b64decode(_pad_base64(s))
1011

11-
# Placeholder for pagination helpers, parse helpers, etc.
12+
# Placeholder for pagination helpers, parse helpers, etc.

fmd_api/models.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from datetime import datetime
33
from typing import Optional, Dict, Any
44

5+
56
@dataclass
67
class Location:
78
lat: float
@@ -15,9 +16,10 @@ class Location:
1516
provider: Optional[str] = None
1617
raw: Optional[Dict[str, Any]] = None
1718

19+
1820
@dataclass
1921
class PhotoResult:
2022
data: bytes
2123
mime_type: str
2224
timestamp: datetime
23-
raw: Optional[Dict[str, Any]] = None
25+
raw: Optional[Dict[str, Any]] = None

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "fmd_api"
3-
version = "2.0.0.dev6"
3+
version = "2.0.0.dev7"
44
authors = [{name = "devinslick"}]
55
description = "A Python client for the FMD (Find My Device) server API"
66
readme = "README.md"

tests/functional/test_auth.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,22 @@
33
Usage:
44
python tests/functional/test_auth.py
55
"""
6+
from tests.utils.read_credentials import read_credentials
67
import asyncio
78
import sys
89
from pathlib import Path
910

1011
# Add repo root to path for package imports
1112
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
1213

13-
from tests.utils.read_credentials import read_credentials
1414

1515
async def main():
1616
creds = read_credentials()
17-
if not creds.get("BASE_URL") or not creds.get("FMD_ID") or not creds.get("PASSWORD"):
18-
print("Missing credentials. Copy tests/utils/credentials.txt.example -> tests/utils/credentials.txt and fill in BASE_URL, FMD_ID, PASSWORD")
17+
if not creds.get("BASE_URL") or not creds.get("FMD_ID") or not creds.get(
18+
"PASSWORD"):
19+
print(
20+
"Missing credentials. Copy tests/utils/credentials.txt.example -> "
21+
"tests/utils/credentials.txt and fill in BASE_URL, FMD_ID, PASSWORD")
1922
return
2023
from fmd_api import FmdClient
2124
client = await FmdClient.create(creds["BASE_URL"], creds["FMD_ID"], creds["PASSWORD"])

0 commit comments

Comments
 (0)