Skip to content

Commit 9d1022c

Browse files
committed
feat: support in-memory files for {from,to}_{json,plist}
1 parent 0e67002 commit 9d1022c

File tree

6 files changed

+47
-23
lines changed

6 files changed

+47
-23
lines changed

findmy/accessory.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from .util import crypto
1919

2020
if TYPE_CHECKING:
21+
import io
2122
from collections.abc import Generator
2223
from pathlib import Path
2324

@@ -269,7 +270,7 @@ def keys_at(self, ind: int) -> set[KeyPair]:
269270
@classmethod
270271
def from_plist(
271272
cls,
272-
plist: str | Path | dict | bytes,
273+
plist: str | Path | dict | bytes | io.BufferedIOBase,
273274
key_alignment_plist: str | Path | dict | bytes | None = None,
274275
*,
275276
name: str | None = None,
@@ -322,7 +323,7 @@ def from_plist(
322323
)
323324

324325
@override
325-
def to_json(self, path: str | Path | None = None, /) -> FindMyAccessoryMapping:
326+
def to_json(self, path: str | Path | io.TextIOBase | None = None, /) -> FindMyAccessoryMapping:
326327
alignment_date = None
327328
if self._alignment_date is not None:
328329
alignment_date = self._alignment_date.isoformat()
@@ -346,7 +347,7 @@ def to_json(self, path: str | Path | None = None, /) -> FindMyAccessoryMapping:
346347
@override
347348
def from_json(
348349
cls,
349-
val: str | Path | FindMyAccessoryMapping,
350+
val: str | Path | io.TextIOBase | io.BufferedIOBase | FindMyAccessoryMapping,
350351
/,
351352
) -> FindMyAccessory:
352353
val = util.files.read_data_json(val)

findmy/keys.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from .util import crypto, parsers
1919

2020
if TYPE_CHECKING:
21+
import io
2122
from collections.abc import Generator
2223
from pathlib import Path
2324

@@ -197,7 +198,7 @@ def adv_key_bytes(self) -> bytes:
197198
return int.to_bytes(key_bytes, 28, "big")
198199

199200
@override
200-
def to_json(self, dst: str | Path | None = None, /) -> KeyPairMapping:
201+
def to_json(self, dst: str | Path | io.TextIOBase | None = None, /) -> KeyPairMapping:
201202
return save_and_return_json(
202203
{
203204
"type": "keypair",
@@ -210,7 +211,9 @@ def to_json(self, dst: str | Path | None = None, /) -> KeyPairMapping:
210211

211212
@classmethod
212213
@override
213-
def from_json(cls, val: str | Path | KeyPairMapping, /) -> KeyPair:
214+
def from_json(
215+
cls, val: str | Path | io.TextIOBase | io.BufferedIOBase | KeyPairMapping, /
216+
) -> KeyPair:
214217
val = read_data_json(val)
215218
assert val["type"] == "keypair"
216219

findmy/reports/account.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
)
4949

5050
if TYPE_CHECKING:
51+
import io
5152
from collections.abc import Sequence
5253
from pathlib import Path
5354

@@ -433,7 +434,7 @@ def last_name(self) -> str | None:
433434
return self._account_info["last_name"] if self._account_info else None
434435

435436
@override
436-
def to_json(self, path: str | Path | None = None, /) -> AccountStateMapping:
437+
def to_json(self, path: str | Path | io.TextIOBase | None = None, /) -> AccountStateMapping:
437438
res: AccountStateMapping = {
438439
"type": "account",
439440
"ids": {"uid": self._uid, "devid": self._devid},
@@ -455,7 +456,7 @@ def to_json(self, path: str | Path | None = None, /) -> AccountStateMapping:
455456
@override
456457
def from_json(
457458
cls,
458-
val: str | Path | AccountStateMapping,
459+
val: str | Path | io.TextIOBase | io.BufferedIOBase | AccountStateMapping,
459460
/,
460461
*,
461462
anisette_libs_path: str | Path | None = None,
@@ -1048,7 +1049,7 @@ def to_json(self, dst: str | Path | None = None, /) -> AccountStateMapping:
10481049
@override
10491050
def from_json(
10501051
cls,
1051-
val: str | Path | AccountStateMapping,
1052+
val: str | Path | io.TextIOBase | io.BufferedIOBase | AccountStateMapping,
10521053
/,
10531054
*,
10541055
anisette_libs_path: str | Path | None = None,

findmy/reports/anisette.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import asyncio
66
import base64
7+
import io
78
import locale
89
import logging
910
import time
@@ -205,7 +206,7 @@ def __init__(self, server_url: str) -> None:
205206
self._closed = False
206207

207208
@override
208-
def to_json(self, dst: str | Path | None = None, /) -> RemoteAnisetteMapping:
209+
def to_json(self, dst: str | Path | io.TextIOBase | None = None, /) -> RemoteAnisetteMapping:
209210
"""See :meth:`BaseAnisetteProvider.serialize`."""
210211
return util.files.save_and_return_json(
211212
{
@@ -217,7 +218,9 @@ def to_json(self, dst: str | Path | None = None, /) -> RemoteAnisetteMapping:
217218

218219
@classmethod
219220
@override
220-
def from_json(cls, val: str | Path | RemoteAnisetteMapping) -> RemoteAnisetteProvider:
221+
def from_json(
222+
cls, val: str | Path | io.TextIOBase | io.BufferedIOBase | RemoteAnisetteMapping
223+
) -> RemoteAnisetteProvider:
221224
"""See :meth:`BaseAnisetteProvider.deserialize`."""
222225
val = util.files.read_data_json(val)
223226

@@ -349,7 +352,7 @@ async def _get_ani(self) -> Anisette:
349352
return ani
350353

351354
@override
352-
def to_json(self, dst: str | Path | None = None, /) -> LocalAnisetteMapping:
355+
def to_json(self, dst: str | Path | io.TextIOBase | None = None, /) -> LocalAnisetteMapping:
353356
"""See :meth:`BaseAnisetteProvider.serialize`."""
354357
if self._ani is None:
355358
# Anisette has not been called yet, so the future has not yet resolved.
@@ -378,7 +381,7 @@ def to_json(self, dst: str | Path | None = None, /) -> LocalAnisetteMapping:
378381
@override
379382
def from_json(
380383
cls,
381-
val: str | Path | LocalAnisetteMapping,
384+
val: str | Path | io.TextIOBase | io.BufferedIOBase | LocalAnisetteMapping,
382385
*,
383386
libs_path: str | Path | None = None,
384387
) -> LocalAnisetteProvider:

findmy/reports/reports.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from findmy.keys import HasHashedPublicKey, KeyPair, KeyPairMapping, KeyPairType
2222

2323
if TYPE_CHECKING:
24+
import io
2425
from collections.abc import Sequence
2526
from pathlib import Path
2627

@@ -199,7 +200,7 @@ def status(self) -> int:
199200
@overload
200201
def to_json(
201202
self,
202-
dst: str | Path | None = None,
203+
dst: str | Path | io.TextIOBase | None = None,
203204
/,
204205
*,
205206
include_key: Literal[True],
@@ -209,7 +210,7 @@ def to_json(
209210
@overload
210211
def to_json(
211212
self,
212-
dst: str | Path | None = None,
213+
dst: str | Path | io.TextIOBase | None = None,
213214
/,
214215
*,
215216
include_key: Literal[False],
@@ -219,7 +220,7 @@ def to_json(
219220
@overload
220221
def to_json(
221222
self,
222-
dst: str | Path | None = None,
223+
dst: str | Path | io.TextIOBase | None = None,
223224
/,
224225
*,
225226
include_key: None = None,
@@ -229,7 +230,7 @@ def to_json(
229230
@override
230231
def to_json(
231232
self,
232-
dst: str | Path | None = None,
233+
dst: str | Path | io.TextIOBase | None = None,
233234
/,
234235
*,
235236
include_key: bool | None = None,
@@ -258,7 +259,9 @@ def to_json(
258259

259260
@classmethod
260261
@override
261-
def from_json(cls, val: str | Path | LocationReportMapping, /) -> LocationReport:
262+
def from_json(
263+
cls, val: str | Path | io.TextIOBase | io.BufferedIOBase | LocationReportMapping, /
264+
) -> LocationReport:
262265
val = util.files.read_data_json(val)
263266
assert val["type"] == "locReportEncrypted" or val["type"] == "locReportDecrypted"
264267

findmy/util/files.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from __future__ import annotations
44

5+
import io
56
import json
67
import plistlib
78
from collections.abc import Mapping
@@ -11,44 +12,53 @@
1112
_T = TypeVar("_T", bound=Mapping)
1213

1314

14-
def save_and_return_json(data: _T, dst: str | Path | None) -> _T:
15+
def save_and_return_json(data: _T, dst: str | Path | io.TextIOBase | None) -> _T:
1516
"""Save and return a JSON-serializable data structure."""
1617
if dst is None:
1718
return data
1819

1920
if isinstance(dst, str):
2021
dst = Path(dst)
2122

22-
dst.write_text(json.dumps(data, indent=4))
23+
if isinstance(dst, io.IOBase):
24+
json.dump(data, dst, indent=4)
25+
elif isinstance(dst, Path):
26+
dst.write_text(json.dumps(data, indent=4))
2327

2428
return data
2529

2630

27-
def read_data_json(val: str | Path | _T) -> _T:
31+
def read_data_json(val: str | Path | io.TextIOBase | io.BufferedIOBase | _T) -> _T:
2832
"""Read JSON data from a file if a path is passed, or return the argument itself."""
2933
if isinstance(val, str):
3034
val = Path(val)
3135

3236
if isinstance(val, Path):
3337
val = cast("_T", json.loads(val.read_text()))
3438

39+
if isinstance(val, io.IOBase):
40+
val = cast("_T", json.load(val))
41+
3542
return val
3643

3744

38-
def save_and_return_plist(data: _T, dst: str | Path | None) -> _T:
45+
def save_and_return_plist(data: _T, dst: str | Path | io.BufferedIOBase | None) -> _T:
3946
"""Save and return a Plist file."""
4047
if dst is None:
4148
return data
4249

4350
if isinstance(dst, str):
4451
dst = Path(dst)
4552

46-
dst.write_bytes(plistlib.dumps(data))
53+
if isinstance(dst, io.IOBase):
54+
dst.write(plistlib.dumps(data))
55+
elif isinstance(dst, Path):
56+
dst.write_bytes(plistlib.dumps(data))
4757

4858
return data
4959

5060

51-
def read_data_plist(val: str | Path | _T | bytes) -> _T:
61+
def read_data_plist(val: str | Path | io.BufferedIOBase | _T | bytes) -> _T:
5262
"""Read Plist data from a file if a path is passed, or return the argument itself."""
5363
if isinstance(val, str):
5464
val = Path(val)
@@ -59,4 +69,7 @@ def read_data_plist(val: str | Path | _T | bytes) -> _T:
5969
if isinstance(val, bytes):
6070
val = cast("_T", plistlib.loads(val))
6171

72+
if isinstance(val, io.IOBase):
73+
val = cast("_T", plistlib.loads(val.read()))
74+
6275
return val

0 commit comments

Comments
 (0)