diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..d90f350 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: [malmeloo] diff --git a/.github/renovate.json b/.github/renovate.json new file mode 100644 index 0000000..6a279c2 --- /dev/null +++ b/.github/renovate.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended", + "schedule:monthly" + ] +} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index f8a3c72..1f2e5fc 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -9,7 +9,6 @@ on: jobs: deploy: runs-on: ubuntu-latest - if: github.ref == 'refs/heads/main' permissions: pages: write @@ -35,7 +34,7 @@ jobs: poetry run make html - name: Setup Pages - uses: actions/configure-pages@v4 + uses: actions/configure-pages@v5 - name: Upload Pages artifact uses: actions/upload-pages-artifact@v3 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 7ff53da..91fc823 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,5 +1,7 @@ name: Upload Python Package +permissions: + contents: write on: workflow_dispatch: @@ -10,7 +12,6 @@ on: jobs: deploy: runs-on: ubuntu-latest - if: github.ref == 'refs/heads/main' steps: - uses: actions/checkout@v4 @@ -32,3 +33,9 @@ jobs: run: | poetry config pypi-token.pypi ${{ secrets.PYPI_API_TOKEN }} poetry publish + + - name: Create release + uses: softprops/action-gh-release@v2 + with: + draft: true + files: dist/* diff --git a/.gitignore b/.gitignore index 4f3a295..a9c36a3 100644 --- a/.gitignore +++ b/.gitignore @@ -161,3 +161,5 @@ cython_debug/ .idea/ account.json +airtag.plist +DO_NOT_COMMIT* diff --git a/README.md b/README.md index b7c07af..4e1f49c 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ application wishing to integrate with the Find My network. - [x] Fetch location reports - [x] Apple acount sign-in - [x] SMS 2FA support + - [x] Trusted Device 2FA support - [x] Scan for nearby FindMy-devices - [x] Decode their info, such as public keys and status bytes - [x] Import or create your own accessory keys @@ -34,8 +35,6 @@ application wishing to integrate with the Find My network. ### Roadmap -- [ ] Trusted device 2FA - - Work has been done, but needs testing (I don't own any Apple devices) - [ ] Local anisette generation (without server) - Can be done using [pyprovision](https://github.com/Dadoum/pyprovision/), however I want to wait until Python wheels are available. @@ -48,7 +47,7 @@ The package can be installed from [PyPi](https://pypi.org/project/findmy/): pip install findmy ``` -For usage examples, see the [examples](examples) directory. Documentation coming soon™. +For usage examples, see the [examples](examples) directory. Documentation can be found [here](http://docs.mikealmel.ooo/FindMy.py/). ## Contributing @@ -68,6 +67,13 @@ pre-commit install After following the above steps, your code will be linted and formatted automatically before committing it. +## Derivative projects + +There are several other cool projects based on this library! Some of them have been listed below, make sure to check them out as well. + +* [OfflineFindRecovery](https://github.com/hajekj/OfflineFindRecovery) - Set of scripts to be able to precisely locate your lost MacBook via Apple's Offline Find through Bluetooth Low Energy. +* [SwiftFindMy](https://github.com/airy10/SwiftFindMy) - Swift port of FindMy.py + ## Credits While I designed the library, the vast majority of actual functionality diff --git a/examples/_login.py b/examples/_login.py new file mode 100644 index 0000000..eb821e0 --- /dev/null +++ b/examples/_login.py @@ -0,0 +1,101 @@ +import json +from pathlib import Path + +from findmy.reports import ( + AppleAccount, + AsyncAppleAccount, + BaseAnisetteProvider, + LoginState, + SmsSecondFactorMethod, + TrustedDeviceSecondFactorMethod, +) + +ACCOUNT_STORE = "account.json" + + +def _login_sync(account: AppleAccount) -> None: + email = input("email? > ") + password = input("passwd? > ") + + state = account.login(email, password) + + if state == LoginState.REQUIRE_2FA: # Account requires 2FA + # This only supports SMS methods for now + methods = account.get_2fa_methods() + + # Print the (masked) phone numbers + for i, method in enumerate(methods): + if isinstance(method, TrustedDeviceSecondFactorMethod): + print(f"{i} - Trusted Device") + elif isinstance(method, SmsSecondFactorMethod): + print(f"{i} - SMS ({method.phone_number})") + + ind = int(input("Method? > ")) + + method = methods[ind] + method.request() + code = input("Code? > ") + + # This automatically finishes the post-2FA login flow + method.submit(code) + + +async def _login_async(account: AsyncAppleAccount) -> None: + email = input("email? > ") + password = input("passwd? > ") + + state = await account.login(email, password) + + if state == LoginState.REQUIRE_2FA: # Account requires 2FA + # This only supports SMS methods for now + methods = await account.get_2fa_methods() + + # Print the (masked) phone numbers + for i, method in enumerate(methods): + if isinstance(method, TrustedDeviceSecondFactorMethod): + print(f"{i} - Trusted Device") + elif isinstance(method, SmsSecondFactorMethod): + print(f"{i} - SMS ({method.phone_number})") + + ind = int(input("Method? > ")) + + method = methods[ind] + await method.request() + code = input("Code? > ") + + # This automatically finishes the post-2FA login flow + await method.submit(code) + + +def get_account_sync(anisette: BaseAnisetteProvider) -> AppleAccount: + """Tries to restore a saved Apple account, or prompts the user for login otherwise. (sync)""" + acc = AppleAccount(anisette) + + # Save / restore account logic + acc_store = Path("account.json") + try: + with acc_store.open() as f: + acc.restore(json.load(f)) + except FileNotFoundError: + _login_sync(acc) + with acc_store.open("w+") as f: + json.dump(acc.export(), f) + + return acc + + +async def get_account_async(anisette: BaseAnisetteProvider) -> AsyncAppleAccount: + """Tries to restore a saved Apple account, or prompts the user for login otherwise. (async)""" + acc = AsyncAppleAccount(anisette) + + # Save / restore account logic + acc_store = Path("account.json") + try: + with acc_store.open() as f: + acc.restore(json.load(f)) + except FileNotFoundError: + await _login_async(acc) + with acc_store.open("w+") as f: + json.dump(acc.export(), f) + + return acc diff --git a/examples/fetch_reports.py b/examples/fetch_reports.py index 82521df..321a775 100644 --- a/examples/fetch_reports.py +++ b/examples/fetch_reports.py @@ -1,22 +1,13 @@ -import json import logging -from pathlib import Path + +from _login import get_account_sync from findmy import KeyPair -from findmy.reports import ( - AppleAccount, - LoginState, - RemoteAnisetteProvider, - SmsSecondFactorMethod, -) +from findmy.reports import RemoteAnisetteProvider # URL to (public or local) anisette server ANISETTE_SERVER = "http://localhost:6969" -# Apple account details -ACCOUNT_EMAIL = "test@test.com" -ACCOUNT_PASS = "" - # Private base64-encoded key to look up KEY_PRIV = "" @@ -26,46 +17,16 @@ KEY_ADV = "" logging.basicConfig(level=logging.DEBUG) -def login(account: AppleAccount) -> None: - state = account.login(ACCOUNT_EMAIL, ACCOUNT_PASS) - - if state == LoginState.REQUIRE_2FA: # Account requires 2FA - # This only supports SMS methods for now - methods = account.get_2fa_methods() - - # Print the (masked) phone numbers - for method in methods: - if isinstance(method, SmsSecondFactorMethod): - print(method.phone_number) - - # Just take the first one to keep things simple - method = methods[0] - method.request() - code = input("Code: ") - - # This automatically finishes the post-2FA login flow - method.submit(code) - - def fetch_reports(lookup_key: KeyPair) -> None: anisette = RemoteAnisetteProvider(ANISETTE_SERVER) - acc = AppleAccount(anisette) - - # Save / restore account logic - acc_store = Path("account.json") - try: - with acc_store.open() as f: - acc.restore(json.load(f)) - except FileNotFoundError: - login(acc) - with acc_store.open("w+") as f: - json.dump(acc.export(), f) + acc = get_account_sync(anisette) print(f"Logged in as: {acc.account_name} ({acc.first_name} {acc.last_name})") # It's that simple! - reports = acc.fetch_last_reports([lookup_key]) - print(reports) + reports = acc.fetch_last_reports([lookup_key])[lookup_key] + for report in sorted(reports): + print(report) if __name__ == "__main__": diff --git a/examples/fetch_reports_async.py b/examples/fetch_reports_async.py index ce24723..754a05f 100644 --- a/examples/fetch_reports_async.py +++ b/examples/fetch_reports_async.py @@ -1,23 +1,14 @@ import asyncio -import json import logging -from pathlib import Path + +from _login import get_account_async from findmy import KeyPair -from findmy.reports import ( - AsyncAppleAccount, - LoginState, - RemoteAnisetteProvider, - SmsSecondFactorMethod, -) +from findmy.reports import RemoteAnisetteProvider # URL to (public or local) anisette server ANISETTE_SERVER = "http://localhost:6969" -# Apple account details -ACCOUNT_EMAIL = "test@test.com" -ACCOUNT_PASS = "" - # Private base64-encoded key to look up KEY_PRIV = "" @@ -27,41 +18,12 @@ KEY_ADV = "" logging.basicConfig(level=logging.DEBUG) -async def login(account: AsyncAppleAccount) -> None: - state = await account.login(ACCOUNT_EMAIL, ACCOUNT_PASS) - - if state == LoginState.REQUIRE_2FA: # Account requires 2FA - # This only supports SMS methods for now - methods = await account.get_2fa_methods() - - # Print the (masked) phone numbers - for method in methods: - if isinstance(method, SmsSecondFactorMethod): - print(method.phone_number) - - # Just take the first one to keep things simple - method = methods[0] - await method.request() - code = input("Code: ") - - # This automatically finishes the post-2FA login flow - await method.submit(code) - - async def fetch_reports(lookup_key: KeyPair) -> None: anisette = RemoteAnisetteProvider(ANISETTE_SERVER) - acc = AsyncAppleAccount(anisette) + + acc = await get_account_async(anisette) try: - acc_store = Path("account.json") - try: - with acc_store.open() as f: - acc.restore(json.load(f)) - except FileNotFoundError: - await login(acc) - with acc_store.open("w+") as f: - json.dump(acc.export(), f) - print(f"Logged in as: {acc.account_name} ({acc.first_name} {acc.last_name})") # It's that simple! diff --git a/examples/real_airtag.py b/examples/real_airtag.py index db03962..e669050 100644 --- a/examples/real_airtag.py +++ b/examples/real_airtag.py @@ -1,17 +1,20 @@ """ -Example showing how to retrieve the primary key of your own AirTag, or any other FindMy-accessory. +Example showing how to fetch locations of an AirTag, or any other FindMy accessory. +""" +from __future__ import annotations -This key can be used to retrieve the device's location for a single day. -""" import plistlib from datetime import datetime, timedelta, timezone from pathlib import Path -from findmy import FindMyAccessory +from _login import get_account_sync + +from findmy import FindMyAccessory, KeyPair +from findmy.reports import RemoteAnisetteProvider + +# URL to (public or local) anisette server +ANISETTE_SERVER = "http://localhost:6969" -# PUBLIC key that the accessory is broadcasting or has previously broadcast. -# For nearby devices, you can use `device_scanner.py` to find it. -PUBLIC_KEY = "" # Path to a .plist dumped from the Find My app. PLIST_PATH = Path("airtag.plist") @@ -29,34 +32,50 @@ SKN = device_data["sharedSecret"]["key"]["data"] # "Secondary" shared secret. 32 bytes. SKS = device_data["secondarySharedSecret"]["key"]["data"] +# "Paired at" timestamp (UTC) +PAIRED_AT = device_data["pairingDate"].replace(tzinfo=timezone.utc) + + +def _gen_keys(airtag: FindMyAccessory, _from: datetime, to: datetime) -> set[KeyPair]: + keys = set() + while _from < to: + keys.update(airtag.keys_at(_from)) + + _from += timedelta(minutes=15) + + return keys + def main() -> None: - paired_at = device_data["pairingDate"].replace(tzinfo=timezone.utc) - airtag = FindMyAccessory(MASTER_KEY, SKN, SKS, paired_at) + # Step 0: create an accessory key generator + airtag = FindMyAccessory(MASTER_KEY, SKN, SKS, PAIRED_AT) - now = datetime.now(tz=timezone.utc) - lookup_time = paired_at.replace( - minute=paired_at.minute // 15 * 15, - second=0, - microsecond=0, - ) + timedelta(minutes=15) + # Step 1: Generate the accessory's private keys, + # starting from 7 days ago until now (12 hour margin) + fetch_to = datetime.now(tz=timezone.utc).astimezone() + timedelta(hours=12) + fetch_from = fetch_to - timedelta(days=8) - while lookup_time < now: - keys = airtag.keys_at(lookup_time) - for key in keys: - if key.adv_key_b64 != PUBLIC_KEY: - continue + print(f"Generating keys from {fetch_from} to {fetch_to} ...") + lookup_keys = _gen_keys(airtag, fetch_from, fetch_to) - print("KEY FOUND!!") - print("KEEP THE BELOW KEY SECRET! IT CAN BE USED TO RETRIEVE THE DEVICE'S LOCATION!") - print(f" - Key: {key.private_key_b64}") - print(f" - Approx. Time: {lookup_time}") - print(f" - Type: {key.key_type}") - return + print(f"Generated {len(lookup_keys)} keys") - lookup_time += timedelta(minutes=15) + # Step 2: log into an Apple account + print("Logging into account") + anisette = RemoteAnisetteProvider(ANISETTE_SERVER) + acc = get_account_sync(anisette) - print("No match found! :(") + # step 3: fetch reports! + print("Fetching reports") + reports = acc.fetch_reports(list(lookup_keys), fetch_from, fetch_to) + + # step 4: print 'em + # reports are in {key: [report]} format, but we only really care about the reports + print() + print("Location reports:") + reports = sorted([r for rs in reports.values() for r in rs]) + for report in reports: + print(f" - {report}") if __name__ == "__main__": diff --git a/findmy/reports/__init__.py b/findmy/reports/__init__.py index b358a97..a5c3851 100644 --- a/findmy/reports/__init__.py +++ b/findmy/reports/__init__.py @@ -1,13 +1,15 @@ """Code related to fetching location reports.""" from .account import AppleAccount, AsyncAppleAccount -from .anisette import RemoteAnisetteProvider +from .anisette import BaseAnisetteProvider, RemoteAnisetteProvider from .state import LoginState -from .twofactor import SmsSecondFactorMethod +from .twofactor import SmsSecondFactorMethod, TrustedDeviceSecondFactorMethod __all__ = ( "AppleAccount", "AsyncAppleAccount", "LoginState", + "BaseAnisetteProvider", "RemoteAnisetteProvider", "SmsSecondFactorMethod", + "TrustedDeviceSecondFactorMethod", ) diff --git a/findmy/reports/account.py b/findmy/reports/account.py index 45048a1..1efb967 100644 --- a/findmy/reports/account.py +++ b/findmy/reports/account.py @@ -3,8 +3,6 @@ from __future__ import annotations import asyncio import base64 -import hashlib -import hmac import json import logging import plistlib @@ -21,16 +19,15 @@ from typing import ( Sequence, TypedDict, TypeVar, + cast, ) import bs4 import srp._pysrp as srp -from cryptography.hazmat.primitives import hashes, padding -from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes -from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from typing_extensions import override from findmy.errors import InvalidCredentialsError, InvalidStateError, UnhandledProtocolError +from findmy.util import crypto from findmy.util.closable import Closable from findmy.util.http import HttpSession, decode_plist @@ -39,9 +36,11 @@ from .state import LoginState from .twofactor import ( AsyncSecondFactorMethod, AsyncSmsSecondFactor, + AsyncTrustedDeviceSecondFactor, BaseSecondFactorMethod, SyncSecondFactorMethod, SyncSmsSecondFactor, + SyncTrustedDeviceSecondFactor, ) if TYPE_CHECKING: @@ -60,6 +59,7 @@ class _AccountInfo(TypedDict): account_name: str first_name: str last_name: str + trusted_device_2fa: bool _P = ParamSpec("_P") @@ -92,32 +92,6 @@ def require_login_state(*states: LoginState) -> Callable[[_F], _F]: return decorator -def _encrypt_password(password: str, salt: bytes, iterations: int) -> bytes: - p = hashlib.sha256(password.encode("utf-8")).digest() - kdf = PBKDF2HMAC( - algorithm=hashes.SHA256(), - length=32, - salt=salt, - iterations=iterations, - ) - return kdf.derive(p) - - -def _decrypt_cbc(session_key: bytes, data: bytes) -> bytes: - extra_data_key = hmac.new(session_key, b"extra data key:", hashlib.sha256).digest() - extra_data_iv = hmac.new(session_key, b"extra data iv:", hashlib.sha256).digest() - # Get only the first 16 bytes of the iv - extra_data_iv = extra_data_iv[:16] - - # Decrypt with AES CBC - cipher = Cipher(algorithms.AES(extra_data_key), modes.CBC(extra_data_iv)) - decryptor = cipher.decryptor() - data = decryptor.update(data) + decryptor.finalize() - # Remove PKCS#7 padding - padder = padding.PKCS7(128).unpadder() - return padder.update(data) + padder.finalize() - - def _extract_phone_numbers(html: str) -> list[dict]: soup = bs4.BeautifulSoup(html, features="html.parser") data_elem = soup.find("script", {"class": "boot_args"}) @@ -223,6 +197,24 @@ class BaseAppleAccount(Closable, ABC): """ raise NotImplementedError + @abstractmethod + def td_2fa_request(self) -> MaybeCoro[None]: + """ + Request a 2FA code to be sent to a trusted device. + + Consider using `BaseSecondFactorMethod.request` instead. + """ + raise NotImplementedError + + @abstractmethod + def td_2fa_submit(self, code: str) -> MaybeCoro[LoginState]: + """ + Submit a 2FA code that was sent to a trusted device. + + Consider using `BaseSecondFactorMethod.submit` instead. + """ + raise NotImplementedError + @abstractmethod def fetch_reports( self, @@ -251,7 +243,11 @@ class BaseAppleAccount(Closable, ABC): raise NotImplementedError @abstractmethod - def get_anisette_headers(self, serial: str = "0") -> MaybeCoro[dict[str, str]]: + def get_anisette_headers( + self, + with_client_info: bool = False, + serial: str = "0", + ) -> MaybeCoro[dict[str, str]]: """ Retrieve a complete dictionary of Anisette headers. @@ -263,6 +259,20 @@ class BaseAppleAccount(Closable, ABC): class AsyncAppleAccount(BaseAppleAccount): """An async implementation of `BaseAppleAccount`.""" + # auth endpoints + _ENDPOINT_GSA = "https://gsa.apple.com/grandslam/GsService2" + _ENDPOINT_LOGIN_MOBILEME = "https://setup.icloud.com/setup/iosbuddy/loginDelegates" + + # 2fa auth endpoints + _ENDPOINT_2FA_METHODS = "https://gsa.apple.com/auth" + _ENDPOINT_2FA_SMS_REQUEST = "https://gsa.apple.com/auth/verify/phone" + _ENDPOINT_2FA_SMS_SUBMIT = "https://gsa.apple.com/auth/verify/phone/securitycode" + _ENDPOINT_2FA_TD_REQUEST = "https://gsa.apple.com/auth/verify/trusteddevice" + _ENDPOINT_2FA_TD_SUBMIT = "https://gsa.apple.com/grandslam/GsService2/validate" + + # reports endpoints + _ENDPOINT_REPORTS_FETCH = "https://gateway.icloud.com/acsnservice/fetch" + def __init__( self, anisette: BaseAnisetteProvider, @@ -409,8 +419,14 @@ class AsyncAppleAccount(BaseAppleAccount): """See `BaseAppleAccount.get_2fa_methods`.""" methods: list[AsyncSecondFactorMethod] = [] + if self._account_info is None: + return [] + + if self._account_info["trusted_device_2fa"]: + methods.append(AsyncTrustedDeviceSecondFactor(self)) + # sms - auth_page = await self._sms_2fa_request("GET", "https://gsa.apple.com/auth") + auth_page = await self._sms_2fa_request("GET", self._ENDPOINT_2FA_METHODS) try: phone_numbers = _extract_phone_numbers(auth_page) methods.extend( @@ -434,7 +450,7 @@ class AsyncAppleAccount(BaseAppleAccount): await self._sms_2fa_request( "PUT", - "https://gsa.apple.com/auth/verify/phone", + self._ENDPOINT_2FA_SMS_REQUEST, data, ) @@ -450,7 +466,7 @@ class AsyncAppleAccount(BaseAppleAccount): await self._sms_2fa_request( "POST", - "https://gsa.apple.com/auth/verify/phone/securitycode", + self._ENDPOINT_2FA_SMS_SUBMIT, data, ) @@ -463,6 +479,44 @@ class AsyncAppleAccount(BaseAppleAccount): # AUTHENTICATED -> LOGGED_IN return await self._login_mobileme() + @require_login_state(LoginState.REQUIRE_2FA) + @override + async def td_2fa_request(self) -> None: + """See `BaseAppleAccount.td_2fa_request`.""" + headers = { + "Content-Type": "text/x-xml-plist", + "Accept": "text/x-xml-plist", + } + await self._sms_2fa_request( + "GET", + self._ENDPOINT_2FA_TD_REQUEST, + headers=headers, + ) + + @require_login_state(LoginState.REQUIRE_2FA) + @override + async def td_2fa_submit(self, code: str) -> LoginState: + """See `BaseAppleAccount.td_2fa_submit`.""" + headers = { + "security-code": code, + "Content-Type": "text/x-xml-plist", + "Accept": "text/x-xml-plist", + } + await self._sms_2fa_request( + "GET", + self._ENDPOINT_2FA_TD_SUBMIT, + headers=headers, + ) + + # REQUIRE_2FA -> AUTHENTICATED + new_state = await self._gsa_authenticate() + if new_state != LoginState.AUTHENTICATED: + msg = f"Unexpected state after submitting 2FA: {new_state}" + raise UnhandledProtocolError(msg) + + # AUTHENTICATED -> LOGGED_IN + return await self._login_mobileme() + @require_login_state(LoginState.LOGGED_IN) async def fetch_raw_reports(self, start: int, end: int, ids: list[str]) -> dict[str, Any]: """Make a request for location reports, returning raw data.""" @@ -471,8 +525,9 @@ class AsyncAppleAccount(BaseAppleAccount): self._login_state_data["mobileme_data"]["tokens"]["searchPartyToken"], ) data = {"search": [{"startDate": start, "endDate": end, "ids": ids}]} + r = await self._http.post( - "https://gateway.icloud.com/acsnservice/fetch", + self._ENDPOINT_REPORTS_FETCH, auth=auth, headers=await self.get_anisette_headers(), json=data, @@ -550,7 +605,7 @@ class AsyncAppleAccount(BaseAppleAccount): logging.debug("Attempting password challenge") - usr.p = _encrypt_password(self._password, r["s"], r["i"]) + usr.p = crypto.encrypt_password(self._password, r["s"], r["i"]) m1 = usr.process_challenge(r["s"], r["B"]) if m1 is None: msg = "Failed to process challenge" @@ -571,37 +626,45 @@ class AsyncAppleAccount(BaseAppleAccount): logging.debug("Decrypting SPD data in response") - spd = _decrypt_cbc(usr.get_session_key() or b"", r["spd"]) - spd = decode_plist(spd) + spd = decode_plist( + crypto.decrypt_spd_aes_cbc( + usr.get_session_key() or b"", + r["spd"], + ), + ) logging.debug("Received account information") - self._account_info = { - "account_name": spd.get("acname"), - "first_name": spd.get("fn"), - "last_name": spd.get("ln"), - } + self._account_info = cast( + _AccountInfo, + { + "account_name": spd.get("acname"), + "first_name": spd.get("fn"), + "last_name": spd.get("ln"), + "trusted_device_2fa": False, + }, + ) - # TODO(malmeloo): support trusted device auth (need account to test) - # https://github.com/malmeloo/FindMy.py/issues/1 au = r["Status"].get("au") - if au in ("secondaryAuth",): + if au in ("secondaryAuth", "trustedDeviceSecondaryAuth"): logging.info("Detected 2FA requirement: %s", au) + self._account_info["trusted_device_2fa"] = au == "trustedDeviceSecondaryAuth" + return self._set_login_state( LoginState.REQUIRE_2FA, {"adsid": spd["adsid"], "idms_token": spd["GsIdmsToken"]}, ) - if au is not None: - msg = f"Unknown auth value: {au}" - raise UnhandledProtocolError(msg) + if au is None: + logging.info("GSA authentication successful") - logging.info("GSA authentication successful") + idms_pet = spd.get("t", {}).get("com.apple.gs.idms.pet", {}).get("token", "") + return self._set_login_state( + LoginState.AUTHENTICATED, + {"idms_pet": idms_pet, "adsid": spd["adsid"]}, + ) - idms_pet = spd.get("t", {}).get("com.apple.gs.idms.pet", {}).get("token", "") - return self._set_login_state( - LoginState.AUTHENTICATED, - {"idms_pet": idms_pet, "adsid": spd["adsid"]}, - ) + msg = f"Unknown auth value: {au}" + raise UnhandledProtocolError(msg) @require_login_state(LoginState.AUTHENTICATED) async def _login_mobileme(self) -> LoginState: @@ -624,7 +687,7 @@ class AsyncAppleAccount(BaseAppleAccount): headers.update(await self.get_anisette_headers()) resp = await self._http.post( - "https://setup.icloud.com/setup/iosbuddy/loginDelegates", + self._ENDPOINT_LOGIN_MOBILEME, auth=(self._username or "", self._login_state_data["idms_pet"]), data=data, headers=headers, @@ -648,26 +711,26 @@ class AsyncAppleAccount(BaseAppleAccount): method: str, url: str, data: dict[str, Any] | None = None, + headers: dict[str, Any] | None = None, ) -> str: adsid = self._login_state_data["adsid"] idms_token = self._login_state_data["idms_token"] identity_token = base64.b64encode((adsid + ":" + idms_token).encode()).decode() - headers = { - "User-Agent": "Xcode", - "Accept-Language": "en-us", - "X-Apple-Identity-Token": identity_token, - "X-Apple-App-Info": "com.apple.gs.xcode.auth", - "X-Xcode-Version": "11.2 (11B41)", - "X-Mme-Client-Info": " " - " ", - } - headers.update(await self.get_anisette_headers()) + headers = headers or {} + headers.update( + { + "User-Agent": "Xcode", + "Accept-Language": "en-us", + "X-Apple-Identity-Token": identity_token, + }, + ) + headers.update(await self.get_anisette_headers(with_client_info=True)) r = await self._http.request( method, url, - json=data or {}, + json=data, headers=headers, ) if not r.ok: @@ -676,34 +739,29 @@ class AsyncAppleAccount(BaseAppleAccount): return r.text() - async def _gsa_request(self, params: dict[str, Any]) -> dict[Any, Any]: - request_data = { - "cpd": { - "bootstrap": True, - "icscrec": True, - "pbe": False, - "prkgen": True, - "svct": "iCloud", - }, - } - request_data["cpd"].update(await self.get_anisette_headers()) - request_data.update(params) - + async def _gsa_request(self, parameters: dict[str, Any]) -> dict[str, Any]: body = { - "Header": {"Version": "1.0.1"}, - "Request": request_data, + "Header": { + "Version": "1.0.1", + }, + "Request": { + "cpd": await self._anisette.get_cpd( + self._uid, + self._devid, + ), + **parameters, + }, } headers = { "Content-Type": "text/x-xml-plist", "Accept": "*/*", "User-Agent": "akd/1.0 CFNetwork/978.0.7 Darwin/18.7.0", - "X-MMe-Client-Info": " " - "", + "X-MMe-Client-Info": self._anisette.client, } resp = await self._http.post( - "https://gsa.apple.com/grandslam/GsService2", + self._ENDPOINT_GSA, headers=headers, data=plistlib.dumps(body), ) @@ -713,9 +771,13 @@ class AsyncAppleAccount(BaseAppleAccount): return resp.plist()["Response"] @override - async def get_anisette_headers(self, serial: str = "0") -> dict[str, str]: + async def get_anisette_headers( + self, + with_client_info: bool = False, + serial: str = "0", + ) -> dict[str, str]: """See `BaseAppleAccount.get_anisette_headers`.""" - return await self._anisette.get_headers(self._uid, self._devid, serial) + return await self._anisette.get_headers(self._uid, self._devid, serial, with_client_info) class AppleAccount(BaseAppleAccount): @@ -797,6 +859,8 @@ class AppleAccount(BaseAppleAccount): for m in methods: if isinstance(m, AsyncSmsSecondFactor): res.append(SyncSmsSecondFactor(self, m.phone_number_id, m.phone_number)) + elif isinstance(m, AsyncTrustedDeviceSecondFactor): + res.append(SyncTrustedDeviceSecondFactor(self)) else: msg = ( f"Failed to cast 2FA object to sync alternative: {m}." @@ -818,6 +882,18 @@ class AppleAccount(BaseAppleAccount): coro = self._asyncacc.sms_2fa_submit(phone_number_id, code) return self._evt_loop.run_until_complete(coro) + @override + def td_2fa_request(self) -> None: + """See `AsyncAppleAccount.td_2fa_request`.""" + coro = self._asyncacc.td_2fa_request() + return self._evt_loop.run_until_complete(coro) + + @override + def td_2fa_submit(self, code: str) -> LoginState: + """See `AsyncAppleAccount.td_2fa_submit`.""" + coro = self._asyncacc.td_2fa_submit(code) + return self._evt_loop.run_until_complete(coro) + @override def fetch_reports( self, @@ -840,7 +916,11 @@ class AppleAccount(BaseAppleAccount): return self._evt_loop.run_until_complete(coro) @override - def get_anisette_headers(self, serial: str = "0") -> dict[str, str]: + def get_anisette_headers( + self, + with_client_info: bool = False, + serial: str = "0", + ) -> dict[str, str]: """See `AsyncAppleAccount.get_anisette_headers`.""" - coro = self._asyncacc.get_anisette_headers(serial) + coro = self._asyncacc.get_anisette_headers(with_client_info, serial) return self._evt_loop.run_until_complete(coro) diff --git a/findmy/reports/anisette.py b/findmy/reports/anisette.py index bdf4b2e..3bfe4e9 100644 --- a/findmy/reports/anisette.py +++ b/findmy/reports/anisette.py @@ -13,48 +13,148 @@ from findmy.util.closable import Closable from findmy.util.http import HttpSession -def _gen_meta_headers( - user_id: str, - device_id: str, - serial: str = "0", -) -> dict[str, str]: - now = datetime.now(tz=timezone.utc) - locale_str = locale.getdefaultlocale()[0] or "en_US" - - return { - "X-Apple-I-Client-Time": now.replace(microsecond=0).isoformat() + "Z", - "X-Apple-I-TimeZone": str(now.astimezone().tzinfo), - "loc": locale_str, - "X-Apple-Locale": locale_str, - "X-Apple-I-MD-RINFO": "17106176", - "X-Apple-I-MD-LU": base64.b64encode(str(user_id).upper().encode()).decode(), - "X-Mme-Device-Id": str(device_id).upper(), - "X-Apple-I-SRL-NO": serial, - } - - class BaseAnisetteProvider(Closable, ABC): - """Abstract base class for Anisette providers.""" + """ + Abstract base class for Anisette providers. + Generously derived from https://github.com/nythepegasus/grandslam/blob/main/src/grandslam/gsa.py#L41. + """ + + @property @abstractmethod - async def _get_base_headers(self) -> dict[str, str]: + def otp(self) -> str: + """ + A seemingly random base64 string containing 28 bytes. + + TODO: Figure out how to generate this. + """ raise NotImplementedError + @property + @abstractmethod + def machine(self) -> str: + """ + A base64 encoded string of 60 'random' bytes. + + We're not sure how this is generated, we have to rely on the server. + TODO: Figure out how to generate this. + """ + raise NotImplementedError + + @property + def timestamp(self) -> str: + """Current timestamp in ISO 8601 format.""" + return datetime.now(tz=timezone.utc).replace(microsecond=0).isoformat() + "Z" + + @property + def timezone(self) -> str: + """Abbreviation of the timezone of the device.""" + return str(datetime.now().astimezone().tzinfo) + + @property + def locale(self) -> str: + """Locale of the device (e.g. en_US).""" + return locale.getdefaultlocale()[0] or "en_US" + + @property + def router(self) -> str: + """ + A number, either 17106176 or 50660608. + + It doesn't seem to matter which one we use. + - 17106176 is used by Sideloadly and Provision (android) based servers. + - 50660608 is used by Windows iCloud based servers. + """ + return "17106176" + + @property + def client(self) -> str: + """ + Client string. + + The format is as follows: + <%MODEL%> <%OS%;%MAJOR%.%MINOR%(%SPMAJOR%,%SPMINOR%);%BUILD%> + <%AUTHKIT_BUNDLE_ID%/%AUTHKIT_VERSION% (%APP_BUNDLE_ID%/%APP_VERSION%)> + + Where: + MODEL: The model of the device (e.g. MacBookPro15,1 or 'PC' + OS: The OS of the device (e.g. Mac OS X or Windows) + MAJOR: The major version of the OS (e.g. 10) + MINOR: The minor version of the OS (e.g. 15) + SPMAJOR: The major version of the service pack (e.g. 0) (Windows only) + SPMINOR: The minor version of the service pack (e.g. 0) (Windows only) + BUILD: The build number of the OS (e.g. 19C57) + AUTHKIT_BUNDLE_ID: The bundle ID of the AuthKit framework (e.g. com.apple.AuthKit) + AUTHKIT_VERSION: The version of the AuthKit framework (e.g. 1) + APP_BUNDLE_ID: The bundle ID of the app (e.g. com.apple.dt.Xcode) + APP_VERSION: The version of the app (e.g. 3594.4.19) + """ + return ( + " " + "" + ) + async def get_headers( self, user_id: str, device_id: str, serial: str = "0", + with_client_info: bool = False, ) -> dict[str, str]: """ - Retrieve a complete dictionary of Anisette headers. + Generate a complete dictionary of Anisette headers. Consider using `BaseAppleAccount.get_anisette_headers` instead. """ - base_headers = await self._get_base_headers() - base_headers.update(_gen_meta_headers(user_id, device_id, serial)) + headers = { + # Current Time + "X-Apple-I-Client-Time": self.timestamp, + "X-Apple-I-TimeZone": self.timezone, + # Locale + "loc": self.locale, + "X-Apple-Locale": self.locale, + # 'One Time Password' + "X-Apple-I-MD": self.otp, + # 'Local User ID' + "X-Apple-I-MD-LU": base64.b64encode(str(user_id).encode()).decode(), + # 'Machine ID' + "X-Apple-I-MD-M": self.machine, + # 'Routing Info', some implementations convert this to an integer + "X-Apple-I-MD-RINFO": self.router, + # 'Device Unique Identifier' + "X-Mme-Device-Id": str(device_id).upper(), + # 'Device Serial Number' + "X-Apple-I-SRL-NO": serial, + } - return base_headers + if with_client_info: + headers["X-Mme-Client-Info"] = self.client + headers["X-Apple-App-Info"] = "com.apple.gs.xcode.auth" + headers["X-Xcode-Version"] = "11.2 (11B41)" + + return headers + + async def get_cpd( + self, + user_id: str, + device_id: str, + serial: str = "0", + ) -> dict[str, str]: + """ + Generate a complete dictionary of CPD data. + + Intended for internal use. + """ + cpd = { + "bootstrap": True, + "icscrec": True, + "pbe": False, + "prkgen": True, + "svct": "iCloud", + } + cpd.update(await self.get_headers(user_id, device_id, serial)) + + return cpd class RemoteAnisetteProvider(BaseAnisetteProvider): @@ -68,17 +168,42 @@ class RemoteAnisetteProvider(BaseAnisetteProvider): self._http = HttpSession() - logging.info("Using remote anisette server: %s", self._server_url) + self._anisette_data: dict[str, str] | None = None + + @property + @override + def otp(self) -> str: + """See `BaseAnisetteProvider.otp`_.""" + otp = (self._anisette_data or {}).get("X-Apple-I-MD") + if otp is None: + logging.warning("X-Apple-I-MD header not found! Returning fallback...") + return otp or "" + + @property + @override + def machine(self) -> str: + """See `BaseAnisetteProvider.machine`_.""" + machine = (self._anisette_data or {}).get("X-Apple-I-MD-M") + if machine is None: + logging.warning("X-Apple-I-MD-M header not found! Returning fallback...") + return machine or "" @override - async def _get_base_headers(self) -> dict[str, str]: - r = await self._http.get(self._server_url) - headers = r.json() + async def get_headers( + self, + user_id: str, + device_id: str, + serial: str = "0", + with_client_info: bool = False, + ) -> dict[str, str]: + """See `BaseAnisetteProvider.get_headers`_.""" + if self._anisette_data is None: + logging.info("Fetching anisette data from %s", self._server_url) - return { - "X-Apple-I-MD": headers["X-Apple-I-MD"], - "X-Apple-I-MD-M": headers["X-Apple-I-MD-M"], - } + r = await self._http.get(self._server_url) + self._anisette_data = r.json() + + return await super().get_headers(user_id, device_id, serial, with_client_info) @override async def close(self) -> None: @@ -91,14 +216,18 @@ class RemoteAnisetteProvider(BaseAnisetteProvider): class LocalAnisetteProvider(BaseAnisetteProvider): """Anisette provider. Generates headers without a remote server using pyprovision.""" - def __init__(self) -> None: - """Initialize the provider.""" - super().__init__() - + @property @override - async def _get_base_headers(self) -> dict[str, str]: - return NotImplemented + def otp(self) -> str: + """See `BaseAnisetteProvider.otp`_.""" + raise NotImplementedError + + @property + @override + def machine(self) -> str: + """See `BaseAnisetteProvider.machine`_.""" + raise NotImplementedError @override async def close(self) -> None: - """See `AnisetteProvider.close`.""" + """See `BaseAnisetteProvider.close`_.""" diff --git a/findmy/reports/reports.py b/findmy/reports/reports.py index b9792f1..a8a83b9 100644 --- a/findmy/reports/reports.py +++ b/findmy/reports/reports.py @@ -3,29 +3,22 @@ from __future__ import annotations import base64 import hashlib +import logging import struct from datetime import datetime, timezone -from typing import TYPE_CHECKING, Sequence, TypedDict, overload +from typing import TYPE_CHECKING, Sequence, overload from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes -from typing_extensions import Unpack, override +from typing_extensions import override from findmy.keys import KeyPair -from findmy.util.http import HttpSession if TYPE_CHECKING: from .account import AsyncAppleAccount -_session = HttpSession() - - -class _FetcherConfig(TypedDict): - user_id: str - device_id: str - dsid: str - search_party_token: str +logging.getLogger(__name__) def _decrypt_payload(payload: bytes, key: KeyPair) -> bytes: @@ -131,7 +124,7 @@ class LocationReport: Requires a `KeyPair` to decrypt the report's payload. """ timestamp_int = int.from_bytes(payload[0:4], "big") + (60 * 60 * 24 * 11323) - timestamp = datetime.fromtimestamp(timestamp_int, tz=timezone.utc) + timestamp = datetime.fromtimestamp(timestamp_int, tz=timezone.utc).astimezone() data = _decrypt_payload(payload, key) latitude = struct.unpack(">i", data[0:4])[0] / 10000000 @@ -181,14 +174,6 @@ class LocationReportsFetcher: """ self._account: AsyncAppleAccount = account - self._http: HttpSession = HttpSession() - - self._config: _FetcherConfig | None = None - - def apply_config(self, **conf: Unpack[_FetcherConfig]) -> None: - """Configure internal variables necessary to make reports fetching calls.""" - self._config = conf - @overload async def fetch_reports( self, @@ -225,8 +210,12 @@ class LocationReportsFetcher: if isinstance(device, KeyPair): return await self._fetch_reports(date_from, date_to, [device]) - # sequence of KeyPairs - reports = await self._fetch_reports(date_from, date_to, device) + # sequence of KeyPairs (fetch 256 max at a time) + reports: list[LocationReport] = [] + for key_offset in range(0, len(device), 256): + chunk = device[key_offset : key_offset + 256] + reports.extend(await self._fetch_reports(date_from, date_to, chunk)) + res: dict[KeyPair, list[LocationReport]] = {key: [] for key in device} for report in reports: res[report.key].append(report) @@ -238,6 +227,8 @@ class LocationReportsFetcher: date_to: datetime, keys: Sequence[KeyPair], ) -> list[LocationReport]: + logging.debug("Fetching reports for %s keys", len(keys)) + start_date = int(date_from.timestamp() * 1000) end_date = int(date_to.timestamp() * 1000) ids = [key.hashed_adv_key_b64 for key in keys] @@ -250,7 +241,7 @@ class LocationReportsFetcher: date_published = datetime.fromtimestamp( report.get("datePublished", 0) / 1000, tz=timezone.utc, - ) + ).astimezone() description = report.get("description", "") payload = base64.b64decode(report["payload"]) diff --git a/findmy/reports/twofactor.py b/findmy/reports/twofactor.py index 005beef..307a3be 100644 --- a/findmy/reports/twofactor.py +++ b/findmy/reports/twofactor.py @@ -122,8 +122,12 @@ class SmsSecondFactorMethod(BaseSecondFactorMethod, ABC): raise NotImplementedError +class TrustedDeviceSecondFactorMethod(BaseSecondFactorMethod, ABC): + """Base class for trusted device-based two-factor authentication.""" + + class AsyncSmsSecondFactor(AsyncSecondFactorMethod, SmsSecondFactorMethod): - """An async implementation of a second-factor method.""" + """An async implementation of `SmsSecondFactorMethod`.""" def __init__( self, @@ -164,16 +168,12 @@ class AsyncSmsSecondFactor(AsyncSecondFactorMethod, SmsSecondFactorMethod): @override async def submit(self, code: str) -> LoginState: - """See `BaseSecondFactorMethod.submit`.""" + """Submit the 2FA code as received over SMS.""" return await self.account.sms_2fa_submit(self._phone_number_id, code) class SyncSmsSecondFactor(SyncSecondFactorMethod, SmsSecondFactorMethod): - """ - A sync implementation of `BaseSecondFactorMethod`. - - Uses `AsyncSmsSecondFactor` internally. - """ + """A sync implementation of `SmsSecondFactorMethod`.""" def __init__( self, @@ -208,3 +208,29 @@ class SyncSmsSecondFactor(SyncSecondFactorMethod, SmsSecondFactorMethod): def submit(self, code: str) -> LoginState: """See `AsyncSmsSecondFactor.submit`.""" return self.account.sms_2fa_submit(self._phone_number_id, code) + + +class AsyncTrustedDeviceSecondFactor(AsyncSecondFactorMethod, TrustedDeviceSecondFactorMethod): + """An async implementation of `TrustedDeviceSecondFactorMethod`.""" + + @override + async def request(self) -> None: + return await self.account.td_2fa_request() + + @override + async def submit(self, code: str) -> LoginState: + return await self.account.td_2fa_submit(code) + + +class SyncTrustedDeviceSecondFactor(SyncSecondFactorMethod, TrustedDeviceSecondFactorMethod): + """A sync implementation of `TrustedDeviceSecondFactorMethod`.""" + + @override + def request(self) -> None: + """See `AsyncTrustedDeviceSecondFactor.request`.""" + return self.account.td_2fa_request() + + @override + def submit(self, code: str) -> LoginState: + """See `AsyncTrustedDeviceSecondFactor.submit`.""" + return self.account.td_2fa_submit(code) diff --git a/findmy/scanner/scanner.py b/findmy/scanner/scanner.py index 0371644..dab1efa 100644 --- a/findmy/scanner/scanner.py +++ b/findmy/scanner/scanner.py @@ -131,7 +131,7 @@ class OfflineFindingScanner: You most likely do not want to use this yourself; check out `OfflineFindingScanner.create` instead. """ - self._scanner: BleakScanner = BleakScanner(self._scan_callback) + self._scanner: BleakScanner = BleakScanner(self._scan_callback, cb={"use_bdaddr": True}) self._loop = loop self._device_fut: asyncio.Future[tuple[BLEDevice, AdvertisementData]] = loop.create_future() @@ -173,7 +173,12 @@ class OfflineFindingScanner: if not apple_data: return None - additional_data = device.details.get("props", {}) + try: + additional_data = device.details.get("props", {}) + except AttributeError: + # Likely Windows host, where details is a '_RawAdvData' object. + # See: https://github.com/malmeloo/FindMy.py/issues/24 + additional_data = {} return OfflineFindingDevice.from_payload(device.address, apple_data, additional_data) async def scan_for( diff --git a/findmy/util/crypto.py b/findmy/util/crypto.py index e14c206..810e92b 100644 --- a/findmy/util/crypto.py +++ b/findmy/util/crypto.py @@ -1,13 +1,44 @@ """Pure-python NIST P-224 Elliptic Curve cryptography. Used for some Apple algorithms.""" -from cryptography.hazmat.primitives import hashes +import hashlib +import hmac + +from cryptography.hazmat.primitives import hashes, padding +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from cryptography.hazmat.primitives.kdf.x963kdf import X963KDF -ECPoint = tuple[float, float] - P224_N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF16A2E0B8F03E13DD29455C5C2A3D +def encrypt_password(password: str, salt: bytes, iterations: int) -> bytes: + """Encrypt password using PBKDF2-HMAC.""" + p = hashlib.sha256(password.encode("utf-8")).digest() + kdf = PBKDF2HMAC( + algorithm=hashes.SHA256(), + length=32, + salt=salt, + iterations=iterations, + ) + return kdf.derive(p) + + +def decrypt_spd_aes_cbc(session_key: bytes, data: bytes) -> bytes: + """Decrypt SPD data using SRP session key.""" + extra_data_key = hmac.new(session_key, b"extra data key:", hashlib.sha256).digest() + extra_data_iv = hmac.new(session_key, b"extra data iv:", hashlib.sha256).digest() + # Get only the first 16 bytes of the iv + extra_data_iv = extra_data_iv[:16] + + # Decrypt with AES CBC + cipher = Cipher(algorithms.AES(extra_data_key), modes.CBC(extra_data_iv)) + decryptor = cipher.decryptor() + data = decryptor.update(data) + decryptor.finalize() + # Remove PKCS#7 padding + padder = padding.PKCS7(128).unpadder() + return padder.update(data) + padder.finalize() + + def x963_kdf(value: bytes, si: bytes, length: int) -> bytes: """Single pass of X9.63 KDF with SHA1.""" return X963KDF( diff --git a/findmy/util/http.py b/findmy/util/http.py index fb87325..0c941e7 100644 --- a/findmy/util/http.py +++ b/findmy/util/http.py @@ -15,7 +15,7 @@ logging.getLogger(__name__) class _HttpRequestOptions(TypedDict, total=False): - json: dict[str, Any] + json: dict[str, Any] | None headers: dict[str, str] auth: tuple[str, str] | BasicAuth data: bytes diff --git a/poetry.lock b/poetry.lock index 3dd32d3..a254b8a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,87 +2,87 @@ [[package]] name = "aiohttp" -version = "3.9.3" +version = "3.9.5" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" files = [ - {file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:939677b61f9d72a4fa2a042a5eee2a99a24001a67c13da113b2e30396567db54"}, - {file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1f5cd333fcf7590a18334c90f8c9147c837a6ec8a178e88d90a9b96ea03194cc"}, - {file = "aiohttp-3.9.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:82e6aa28dd46374f72093eda8bcd142f7771ee1eb9d1e223ff0fa7177a96b4a5"}, - {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f56455b0c2c7cc3b0c584815264461d07b177f903a04481dfc33e08a89f0c26b"}, - {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bca77a198bb6e69795ef2f09a5f4c12758487f83f33d63acde5f0d4919815768"}, - {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e083c285857b78ee21a96ba1eb1b5339733c3563f72980728ca2b08b53826ca5"}, - {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab40e6251c3873d86ea9b30a1ac6d7478c09277b32e14745d0d3c6e76e3c7e29"}, - {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df822ee7feaaeffb99c1a9e5e608800bd8eda6e5f18f5cfb0dc7eeb2eaa6bbec"}, - {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:acef0899fea7492145d2bbaaaec7b345c87753168589cc7faf0afec9afe9b747"}, - {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cd73265a9e5ea618014802ab01babf1940cecb90c9762d8b9e7d2cc1e1969ec6"}, - {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a78ed8a53a1221393d9637c01870248a6f4ea5b214a59a92a36f18151739452c"}, - {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:6b0e029353361f1746bac2e4cc19b32f972ec03f0f943b390c4ab3371840aabf"}, - {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7cf5c9458e1e90e3c390c2639f1017a0379a99a94fdfad3a1fd966a2874bba52"}, - {file = "aiohttp-3.9.3-cp310-cp310-win32.whl", hash = "sha256:3e59c23c52765951b69ec45ddbbc9403a8761ee6f57253250c6e1536cacc758b"}, - {file = "aiohttp-3.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:055ce4f74b82551678291473f66dc9fb9048a50d8324278751926ff0ae7715e5"}, - {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6b88f9386ff1ad91ace19d2a1c0225896e28815ee09fc6a8932fded8cda97c3d"}, - {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c46956ed82961e31557b6857a5ca153c67e5476972e5f7190015018760938da2"}, - {file = "aiohttp-3.9.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:07b837ef0d2f252f96009e9b8435ec1fef68ef8b1461933253d318748ec1acdc"}, - {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad46e6f620574b3b4801c68255492e0159d1712271cc99d8bdf35f2043ec266"}, - {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ed3e046ea7b14938112ccd53d91c1539af3e6679b222f9469981e3dac7ba1ce"}, - {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:039df344b45ae0b34ac885ab5b53940b174530d4dd8a14ed8b0e2155b9dddccb"}, - {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7943c414d3a8d9235f5f15c22ace69787c140c80b718dcd57caaade95f7cd93b"}, - {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84871a243359bb42c12728f04d181a389718710129b36b6aad0fc4655a7647d4"}, - {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5eafe2c065df5401ba06821b9a054d9cb2848867f3c59801b5d07a0be3a380ae"}, - {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9d3c9b50f19704552f23b4eaea1fc082fdd82c63429a6506446cbd8737823da3"}, - {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:f033d80bc6283092613882dfe40419c6a6a1527e04fc69350e87a9df02bbc283"}, - {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:2c895a656dd7e061b2fd6bb77d971cc38f2afc277229ce7dd3552de8313a483e"}, - {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1f5a71d25cd8106eab05f8704cd9167b6e5187bcdf8f090a66c6d88b634802b4"}, - {file = "aiohttp-3.9.3-cp311-cp311-win32.whl", hash = "sha256:50fca156d718f8ced687a373f9e140c1bb765ca16e3d6f4fe116e3df7c05b2c5"}, - {file = "aiohttp-3.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:5fe9ce6c09668063b8447f85d43b8d1c4e5d3d7e92c63173e6180b2ac5d46dd8"}, - {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:38a19bc3b686ad55804ae931012f78f7a534cce165d089a2059f658f6c91fa60"}, - {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:770d015888c2a598b377bd2f663adfd947d78c0124cfe7b959e1ef39f5b13869"}, - {file = "aiohttp-3.9.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee43080e75fc92bf36219926c8e6de497f9b247301bbf88c5c7593d931426679"}, - {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52df73f14ed99cee84865b95a3d9e044f226320a87af208f068ecc33e0c35b96"}, - {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc9b311743a78043b26ffaeeb9715dc360335e5517832f5a8e339f8a43581e4d"}, - {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b955ed993491f1a5da7f92e98d5dad3c1e14dc175f74517c4e610b1f2456fb11"}, - {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:504b6981675ace64c28bf4a05a508af5cde526e36492c98916127f5a02354d53"}, - {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6fe5571784af92b6bc2fda8d1925cccdf24642d49546d3144948a6a1ed58ca5"}, - {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ba39e9c8627edc56544c8628cc180d88605df3892beeb2b94c9bc857774848ca"}, - {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e5e46b578c0e9db71d04c4b506a2121c0cb371dd89af17a0586ff6769d4c58c1"}, - {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:938a9653e1e0c592053f815f7028e41a3062e902095e5a7dc84617c87267ebd5"}, - {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:c3452ea726c76e92f3b9fae4b34a151981a9ec0a4847a627c43d71a15ac32aa6"}, - {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ff30218887e62209942f91ac1be902cc80cddb86bf00fbc6783b7a43b2bea26f"}, - {file = "aiohttp-3.9.3-cp312-cp312-win32.whl", hash = "sha256:38f307b41e0bea3294a9a2a87833191e4bcf89bb0365e83a8be3a58b31fb7f38"}, - {file = "aiohttp-3.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:b791a3143681a520c0a17e26ae7465f1b6f99461a28019d1a2f425236e6eedb5"}, - {file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ed621426d961df79aa3b963ac7af0d40392956ffa9be022024cd16297b30c8c"}, - {file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7f46acd6a194287b7e41e87957bfe2ad1ad88318d447caf5b090012f2c5bb528"}, - {file = "aiohttp-3.9.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:feeb18a801aacb098220e2c3eea59a512362eb408d4afd0c242044c33ad6d542"}, - {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f734e38fd8666f53da904c52a23ce517f1b07722118d750405af7e4123933511"}, - {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b40670ec7e2156d8e57f70aec34a7216407848dfe6c693ef131ddf6e76feb672"}, - {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fdd215b7b7fd4a53994f238d0f46b7ba4ac4c0adb12452beee724ddd0743ae5d"}, - {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:017a21b0df49039c8f46ca0971b3a7fdc1f56741ab1240cb90ca408049766168"}, - {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e99abf0bba688259a496f966211c49a514e65afa9b3073a1fcee08856e04425b"}, - {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:648056db9a9fa565d3fa851880f99f45e3f9a771dd3ff3bb0c048ea83fb28194"}, - {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8aacb477dc26797ee089721536a292a664846489c49d3ef9725f992449eda5a8"}, - {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:522a11c934ea660ff8953eda090dcd2154d367dec1ae3c540aff9f8a5c109ab4"}, - {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5bce0dc147ca85caa5d33debc4f4d65e8e8b5c97c7f9f660f215fa74fc49a321"}, - {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b4af9f25b49a7be47c0972139e59ec0e8285c371049df1a63b6ca81fdd216a2"}, - {file = "aiohttp-3.9.3-cp38-cp38-win32.whl", hash = "sha256:298abd678033b8571995650ccee753d9458dfa0377be4dba91e4491da3f2be63"}, - {file = "aiohttp-3.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:69361bfdca5468c0488d7017b9b1e5ce769d40b46a9f4a2eed26b78619e9396c"}, - {file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0fa43c32d1643f518491d9d3a730f85f5bbaedcbd7fbcae27435bb8b7a061b29"}, - {file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:835a55b7ca49468aaaac0b217092dfdff370e6c215c9224c52f30daaa735c1c1"}, - {file = "aiohttp-3.9.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06a9b2c8837d9a94fae16c6223acc14b4dfdff216ab9b7202e07a9a09541168f"}, - {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abf151955990d23f84205286938796c55ff11bbfb4ccfada8c9c83ae6b3c89a3"}, - {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59c26c95975f26e662ca78fdf543d4eeaef70e533a672b4113dd888bd2423caa"}, - {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f95511dd5d0e05fd9728bac4096319f80615aaef4acbecb35a990afebe953b0e"}, - {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:595f105710293e76b9dc09f52e0dd896bd064a79346234b521f6b968ffdd8e58"}, - {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7c8b816c2b5af5c8a436df44ca08258fc1a13b449393a91484225fcb7545533"}, - {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f1088fa100bf46e7b398ffd9904f4808a0612e1d966b4aa43baa535d1b6341eb"}, - {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f59dfe57bb1ec82ac0698ebfcdb7bcd0e99c255bd637ff613760d5f33e7c81b3"}, - {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:361a1026c9dd4aba0109e4040e2aecf9884f5cfe1b1b1bd3d09419c205e2e53d"}, - {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:363afe77cfcbe3a36353d8ea133e904b108feea505aa4792dad6585a8192c55a"}, - {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e2c45c208c62e955e8256949eb225bd8b66a4c9b6865729a786f2aa79b72e9d"}, - {file = "aiohttp-3.9.3-cp39-cp39-win32.whl", hash = "sha256:f7217af2e14da0856e082e96ff637f14ae45c10a5714b63c77f26d8884cf1051"}, - {file = "aiohttp-3.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:27468897f628c627230dba07ec65dc8d0db566923c48f29e084ce382119802bc"}, - {file = "aiohttp-3.9.3.tar.gz", hash = "sha256:90842933e5d1ff760fae6caca4b2b3edba53ba8f4b71e95dacf2818a2aca06f7"}, + {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fcde4c397f673fdec23e6b05ebf8d4751314fa7c24f93334bf1f1364c1c69ac7"}, + {file = "aiohttp-3.9.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d6b3f1fabe465e819aed2c421a6743d8debbde79b6a8600739300630a01bf2c"}, + {file = "aiohttp-3.9.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ae79c1bc12c34082d92bf9422764f799aee4746fd7a392db46b7fd357d4a17a"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d3ebb9e1316ec74277d19c5f482f98cc65a73ccd5430540d6d11682cd857430"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84dabd95154f43a2ea80deffec9cb44d2e301e38a0c9d331cc4aa0166fe28ae3"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8a02fbeca6f63cb1f0475c799679057fc9268b77075ab7cf3f1c600e81dd46b"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c26959ca7b75ff768e2776d8055bf9582a6267e24556bb7f7bd29e677932be72"}, + {file = "aiohttp-3.9.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:714d4e5231fed4ba2762ed489b4aec07b2b9953cf4ee31e9871caac895a839c0"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7a6a8354f1b62e15d48e04350f13e726fa08b62c3d7b8401c0a1314f02e3558"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c413016880e03e69d166efb5a1a95d40f83d5a3a648d16486592c49ffb76d0db"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ff84aeb864e0fac81f676be9f4685f0527b660f1efdc40dcede3c251ef1e867f"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ad7f2919d7dac062f24d6f5fe95d401597fbb015a25771f85e692d043c9d7832"}, + {file = "aiohttp-3.9.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:702e2c7c187c1a498a4e2b03155d52658fdd6fda882d3d7fbb891a5cf108bb10"}, + {file = "aiohttp-3.9.5-cp310-cp310-win32.whl", hash = "sha256:67c3119f5ddc7261d47163ed86d760ddf0e625cd6246b4ed852e82159617b5fb"}, + {file = "aiohttp-3.9.5-cp310-cp310-win_amd64.whl", hash = "sha256:471f0ef53ccedec9995287f02caf0c068732f026455f07db3f01a46e49d76bbb"}, + {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ae53e33ee7476dd3d1132f932eeb39bf6125083820049d06edcdca4381f342"}, + {file = "aiohttp-3.9.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c088c4d70d21f8ca5c0b8b5403fe84a7bc8e024161febdd4ef04575ef35d474d"}, + {file = "aiohttp-3.9.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:639d0042b7670222f33b0028de6b4e2fad6451462ce7df2af8aee37dcac55424"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f26383adb94da5e7fb388d441bf09c61e5e35f455a3217bfd790c6b6bc64b2ee"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66331d00fb28dc90aa606d9a54304af76b335ae204d1836f65797d6fe27f1ca2"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ff550491f5492ab5ed3533e76b8567f4b37bd2995e780a1f46bca2024223233"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f22eb3a6c1080d862befa0a89c380b4dafce29dc6cd56083f630073d102eb595"}, + {file = "aiohttp-3.9.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a81b1143d42b66ffc40a441379387076243ef7b51019204fd3ec36b9f69e77d6"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f64fd07515dad67f24b6ea4a66ae2876c01031de91c93075b8093f07c0a2d93d"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:93e22add827447d2e26d67c9ac0161756007f152fdc5210277d00a85f6c92323"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:55b39c8684a46e56ef8c8d24faf02de4a2b2ac60d26cee93bc595651ff545de9"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4715a9b778f4293b9f8ae7a0a7cef9829f02ff8d6277a39d7f40565c737d3771"}, + {file = "aiohttp-3.9.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:afc52b8d969eff14e069a710057d15ab9ac17cd4b6753042c407dcea0e40bf75"}, + {file = "aiohttp-3.9.5-cp311-cp311-win32.whl", hash = "sha256:b3df71da99c98534be076196791adca8819761f0bf6e08e07fd7da25127150d6"}, + {file = "aiohttp-3.9.5-cp311-cp311-win_amd64.whl", hash = "sha256:88e311d98cc0bf45b62fc46c66753a83445f5ab20038bcc1b8a1cc05666f428a"}, + {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c7a4b7a6cf5b6eb11e109a9755fd4fda7d57395f8c575e166d363b9fc3ec4678"}, + {file = "aiohttp-3.9.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0a158704edf0abcac8ac371fbb54044f3270bdbc93e254a82b6c82be1ef08f3c"}, + {file = "aiohttp-3.9.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d153f652a687a8e95ad367a86a61e8d53d528b0530ef382ec5aaf533140ed00f"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82a6a97d9771cb48ae16979c3a3a9a18b600a8505b1115cfe354dfb2054468b4"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60cdbd56f4cad9f69c35eaac0fbbdf1f77b0ff9456cebd4902f3dd1cf096464c"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8676e8fd73141ded15ea586de0b7cda1542960a7b9ad89b2b06428e97125d4fa"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da00da442a0e31f1c69d26d224e1efd3a1ca5bcbf210978a2ca7426dfcae9f58"}, + {file = "aiohttp-3.9.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18f634d540dd099c262e9f887c8bbacc959847cfe5da7a0e2e1cf3f14dbf2daf"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:320e8618eda64e19d11bdb3bd04ccc0a816c17eaecb7e4945d01deee2a22f95f"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2faa61a904b83142747fc6a6d7ad8fccff898c849123030f8e75d5d967fd4a81"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8c64a6dc3fe5db7b1b4d2b5cb84c4f677768bdc340611eca673afb7cf416ef5a"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:393c7aba2b55559ef7ab791c94b44f7482a07bf7640d17b341b79081f5e5cd1a"}, + {file = "aiohttp-3.9.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c671dc117c2c21a1ca10c116cfcd6e3e44da7fcde37bf83b2be485ab377b25da"}, + {file = "aiohttp-3.9.5-cp312-cp312-win32.whl", hash = "sha256:5a7ee16aab26e76add4afc45e8f8206c95d1d75540f1039b84a03c3b3800dd59"}, + {file = "aiohttp-3.9.5-cp312-cp312-win_amd64.whl", hash = "sha256:5ca51eadbd67045396bc92a4345d1790b7301c14d1848feaac1d6a6c9289e888"}, + {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:694d828b5c41255e54bc2dddb51a9f5150b4eefa9886e38b52605a05d96566e8"}, + {file = "aiohttp-3.9.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0605cc2c0088fcaae79f01c913a38611ad09ba68ff482402d3410bf59039bfb8"}, + {file = "aiohttp-3.9.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4558e5012ee03d2638c681e156461d37b7a113fe13970d438d95d10173d25f78"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dbc053ac75ccc63dc3a3cc547b98c7258ec35a215a92bd9f983e0aac95d3d5b"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4109adee842b90671f1b689901b948f347325045c15f46b39797ae1bf17019de"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6ea1a5b409a85477fd8e5ee6ad8f0e40bf2844c270955e09360418cfd09abac"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3c2890ca8c59ee683fd09adf32321a40fe1cf164e3387799efb2acebf090c11"}, + {file = "aiohttp-3.9.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3916c8692dbd9d55c523374a3b8213e628424d19116ac4308e434dbf6d95bbdd"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8d1964eb7617907c792ca00b341b5ec3e01ae8c280825deadbbd678447b127e1"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5ab8e1f6bee051a4bf6195e38a5c13e5e161cb7bad83d8854524798bd9fcd6e"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:52c27110f3862a1afbcb2af4281fc9fdc40327fa286c4625dfee247c3ba90156"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7f64cbd44443e80094309875d4f9c71d0401e966d191c3d469cde4642bc2e031"}, + {file = "aiohttp-3.9.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8b4f72fbb66279624bfe83fd5eb6aea0022dad8eec62b71e7bf63ee1caadeafe"}, + {file = "aiohttp-3.9.5-cp38-cp38-win32.whl", hash = "sha256:6380c039ec52866c06d69b5c7aad5478b24ed11696f0e72f6b807cfb261453da"}, + {file = "aiohttp-3.9.5-cp38-cp38-win_amd64.whl", hash = "sha256:da22dab31d7180f8c3ac7c7635f3bcd53808f374f6aa333fe0b0b9e14b01f91a"}, + {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1732102949ff6087589408d76cd6dea656b93c896b011ecafff418c9661dc4ed"}, + {file = "aiohttp-3.9.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6021d296318cb6f9414b48e6a439a7f5d1f665464da507e8ff640848ee2a58a"}, + {file = "aiohttp-3.9.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:239f975589a944eeb1bad26b8b140a59a3a320067fb3cd10b75c3092405a1372"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b7b30258348082826d274504fbc7c849959f1989d86c29bc355107accec6cfb"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2adf5c87ff6d8b277814a28a535b59e20bfea40a101db6b3bdca7e9926bc24"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a3d838441bebcf5cf442700e3963f58b5c33f015341f9ea86dcd7d503c07e2"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e3a1ae66e3d0c17cf65c08968a5ee3180c5a95920ec2731f53343fac9bad106"}, + {file = "aiohttp-3.9.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c69e77370cce2d6df5d12b4e12bdcca60c47ba13d1cbbc8645dd005a20b738b"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf56238f4bbf49dab8c2dc2e6b1b68502b1e88d335bea59b3f5b9f4c001475"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d1469f228cd9ffddd396d9948b8c9cd8022b6d1bf1e40c6f25b0fb90b4f893ed"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:45731330e754f5811c314901cebdf19dd776a44b31927fa4b4dbecab9e457b0c"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:3fcb4046d2904378e3aeea1df51f697b0467f2aac55d232c87ba162709478c46"}, + {file = "aiohttp-3.9.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8cf142aa6c1a751fcb364158fd710b8a9be874b81889c2bd13aa8893197455e2"}, + {file = "aiohttp-3.9.5-cp39-cp39-win32.whl", hash = "sha256:7b179eea70833c8dee51ec42f3b4097bd6370892fa93f510f76762105568cf09"}, + {file = "aiohttp-3.9.5-cp39-cp39-win_amd64.whl", hash = "sha256:38d80498e2e169bc61418ff36170e0aad0cd268da8b38a17c4cf29d254a8b3f1"}, + {file = "aiohttp-3.9.5.tar.gz", hash = "sha256:edea7d15772ceeb29db4aff55e482d4bcfb6ae160ce144f2682de02f6d693551"}, ] [package.dependencies] @@ -134,13 +134,13 @@ files = [ [[package]] name = "astroid" -version = "3.0.3" +version = "3.1.0" description = "An abstract syntax tree for Python with inference support." optional = false python-versions = ">=3.8.0" files = [ - {file = "astroid-3.0.3-py3-none-any.whl", hash = "sha256:92fcf218b89f449cdf9f7b39a269f8d5d617b27be68434912e11e79203963a17"}, - {file = "astroid-3.0.3.tar.gz", hash = "sha256:4148645659b08b70d72460ed1921158027a9e53ae8b7234149b1400eddacbb93"}, + {file = "astroid-3.1.0-py3-none-any.whl", hash = "sha256:951798f922990137ac090c53af473db7ab4e70c770e6d7fae0cec59f74411819"}, + {file = "astroid-3.1.0.tar.gz", hash = "sha256:ac248253bfa4bd924a0de213707e7ebeeb3138abeb48d798784ead1e56d419d4"}, ] [package.dependencies] @@ -456,43 +456,43 @@ files = [ [[package]] name = "cryptography" -version = "42.0.2" +version = "42.0.5" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-42.0.2-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:701171f825dcab90969596ce2af253143b93b08f1a716d4b2a9d2db5084ef7be"}, - {file = "cryptography-42.0.2-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:61321672b3ac7aade25c40449ccedbc6db72c7f5f0fdf34def5e2f8b51ca530d"}, - {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea2c3ffb662fec8bbbfce5602e2c159ff097a4631d96235fcf0fb00e59e3ece4"}, - {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b15c678f27d66d247132cbf13df2f75255627bcc9b6a570f7d2fd08e8c081d2"}, - {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8e88bb9eafbf6a4014d55fb222e7360eef53e613215085e65a13290577394529"}, - {file = "cryptography-42.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a047682d324ba56e61b7ea7c7299d51e61fd3bca7dad2ccc39b72bd0118d60a1"}, - {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:36d4b7c4be6411f58f60d9ce555a73df8406d484ba12a63549c88bd64f7967f1"}, - {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:a00aee5d1b6c20620161984f8ab2ab69134466c51f58c052c11b076715e72929"}, - {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b97fe7d7991c25e6a31e5d5e795986b18fbbb3107b873d5f3ae6dc9a103278e9"}, - {file = "cryptography-42.0.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5fa82a26f92871eca593b53359c12ad7949772462f887c35edaf36f87953c0e2"}, - {file = "cryptography-42.0.2-cp37-abi3-win32.whl", hash = "sha256:4b063d3413f853e056161eb0c7724822a9740ad3caa24b8424d776cebf98e7ee"}, - {file = "cryptography-42.0.2-cp37-abi3-win_amd64.whl", hash = "sha256:841ec8af7a8491ac76ec5a9522226e287187a3107e12b7d686ad354bb78facee"}, - {file = "cryptography-42.0.2-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:55d1580e2d7e17f45d19d3b12098e352f3a37fe86d380bf45846ef257054b242"}, - {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28cb2c41f131a5758d6ba6a0504150d644054fd9f3203a1e8e8d7ac3aea7f73a"}, - {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9097a208875fc7bbeb1286d0125d90bdfed961f61f214d3f5be62cd4ed8a446"}, - {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:44c95c0e96b3cb628e8452ec060413a49002a247b2b9938989e23a2c8291fc90"}, - {file = "cryptography-42.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2f9f14185962e6a04ab32d1abe34eae8a9001569ee4edb64d2304bf0d65c53f3"}, - {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:09a77e5b2e8ca732a19a90c5bca2d124621a1edb5438c5daa2d2738bfeb02589"}, - {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ad28cff53f60d99a928dfcf1e861e0b2ceb2bc1f08a074fdd601b314e1cc9e0a"}, - {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:130c0f77022b2b9c99d8cebcdd834d81705f61c68e91ddd614ce74c657f8b3ea"}, - {file = "cryptography-42.0.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:fa3dec4ba8fb6e662770b74f62f1a0c7d4e37e25b58b2bf2c1be4c95372b4a33"}, - {file = "cryptography-42.0.2-cp39-abi3-win32.whl", hash = "sha256:3dbd37e14ce795b4af61b89b037d4bc157f2cb23e676fa16932185a04dfbf635"}, - {file = "cryptography-42.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:8a06641fb07d4e8f6c7dda4fc3f8871d327803ab6542e33831c7ccfdcb4d0ad6"}, - {file = "cryptography-42.0.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:087887e55e0b9c8724cf05361357875adb5c20dec27e5816b653492980d20380"}, - {file = "cryptography-42.0.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a7ef8dd0bf2e1d0a27042b231a3baac6883cdd5557036f5e8df7139255feaac6"}, - {file = "cryptography-42.0.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4383b47f45b14459cab66048d384614019965ba6c1a1a141f11b5a551cace1b2"}, - {file = "cryptography-42.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:fbeb725c9dc799a574518109336acccaf1303c30d45c075c665c0793c2f79a7f"}, - {file = "cryptography-42.0.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:320948ab49883557a256eab46149df79435a22d2fefd6a66fe6946f1b9d9d008"}, - {file = "cryptography-42.0.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5ef9bc3d046ce83c4bbf4c25e1e0547b9c441c01d30922d812e887dc5f125c12"}, - {file = "cryptography-42.0.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:52ed9ebf8ac602385126c9a2fe951db36f2cb0c2538d22971487f89d0de4065a"}, - {file = "cryptography-42.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:141e2aa5ba100d3788c0ad7919b288f89d1fe015878b9659b307c9ef867d3a65"}, - {file = "cryptography-42.0.2.tar.gz", hash = "sha256:e0ec52ba3c7f1b7d813cd52649a5b3ef1fc0d433219dc8c93827c57eab6cf888"}, + {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:a30596bae9403a342c978fb47d9b0ee277699fa53bbafad14706af51fe543d16"}, + {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b7ffe927ee6531c78f81aa17e684e2ff617daeba7f189f911065b2ea2d526dec"}, + {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2424ff4c4ac7f6b8177b53c17ed5d8fa74ae5955656867f5a8affaca36a27abb"}, + {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:329906dcc7b20ff3cad13c069a78124ed8247adcac44b10bea1130e36caae0b4"}, + {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:b03c2ae5d2f0fc05f9a2c0c997e1bc18c8229f392234e8a0194f202169ccd278"}, + {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8837fe1d6ac4a8052a9a8ddab256bc006242696f03368a4009be7ee3075cdb7"}, + {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:0270572b8bd2c833c3981724b8ee9747b3ec96f699a9665470018594301439ee"}, + {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:b8cac287fafc4ad485b8a9b67d0ee80c66bf3574f655d3b97ef2e1082360faf1"}, + {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:16a48c23a62a2f4a285699dba2e4ff2d1cff3115b9df052cdd976a18856d8e3d"}, + {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2bce03af1ce5a5567ab89bd90d11e7bbdff56b8af3acbbec1faded8f44cb06da"}, + {file = "cryptography-42.0.5-cp37-abi3-win32.whl", hash = "sha256:b6cd2203306b63e41acdf39aa93b86fb566049aeb6dc489b70e34bcd07adca74"}, + {file = "cryptography-42.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:98d8dc6d012b82287f2c3d26ce1d2dd130ec200c8679b6213b3c73c08b2b7940"}, + {file = "cryptography-42.0.5-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:5e6275c09d2badf57aea3afa80d975444f4be8d3bc58f7f80d2a484c6f9485c8"}, + {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4985a790f921508f36f81831817cbc03b102d643b5fcb81cd33df3fa291a1a1"}, + {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cde5f38e614f55e28d831754e8a3bacf9ace5d1566235e39d91b35502d6936e"}, + {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7367d7b2eca6513681127ebad53b2582911d1736dc2ffc19f2c3ae49997496bc"}, + {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cd2030f6650c089aeb304cf093f3244d34745ce0cfcc39f20c6fbfe030102e2a"}, + {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a2913c5375154b6ef2e91c10b5720ea6e21007412f6437504ffea2109b5a33d7"}, + {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:c41fb5e6a5fe9ebcd58ca3abfeb51dffb5d83d6775405305bfa8715b76521922"}, + {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3eaafe47ec0d0ffcc9349e1708be2aaea4c6dd4978d76bf6eb0cb2c13636c6fc"}, + {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1b95b98b0d2af784078fa69f637135e3c317091b615cd0905f8b8a087e86fa30"}, + {file = "cryptography-42.0.5-cp39-abi3-win32.whl", hash = "sha256:1f71c10d1e88467126f0efd484bd44bca5e14c664ec2ede64c32f20875c0d413"}, + {file = "cryptography-42.0.5-cp39-abi3-win_amd64.whl", hash = "sha256:a011a644f6d7d03736214d38832e030d8268bcff4a41f728e6030325fea3e400"}, + {file = "cryptography-42.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9481ffe3cf013b71b2428b905c4f7a9a4f76ec03065b05ff499bb5682a8d9ad8"}, + {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:ba334e6e4b1d92442b75ddacc615c5476d4ad55cc29b15d590cc6b86efa487e2"}, + {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ba3e4a42397c25b7ff88cdec6e2a16c2be18720f317506ee25210f6d31925f9c"}, + {file = "cryptography-42.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:111a0d8553afcf8eb02a4fea6ca4f59d48ddb34497aa8706a6cf536f1a5ec576"}, + {file = "cryptography-42.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cd65d75953847815962c84a4654a84850b2bb4aed3f26fadcc1c13892e1e29f6"}, + {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e807b3188f9eb0eaa7bbb579b462c5ace579f1cedb28107ce8b48a9f7ad3679e"}, + {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f12764b8fffc7a123f641d7d049d382b73f96a34117e0b637b80643169cec8ac"}, + {file = "cryptography-42.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:37dd623507659e08be98eec89323469e8c7b4c1407c85112634ae3dbdb926fdd"}, + {file = "cryptography-42.0.5.tar.gz", hash = "sha256:6fe07eec95dfd477eb9530aef5bead34fec819b3aaf6c5bd6d20565da607bfe1"}, ] [package.dependencies] @@ -575,18 +575,18 @@ files = [ [[package]] name = "filelock" -version = "3.13.1" +version = "3.13.4" description = "A platform independent file lock." optional = false python-versions = ">=3.8" files = [ - {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"}, - {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"}, + {file = "filelock-3.13.4-py3-none-any.whl", hash = "sha256:404e5e9253aa60ad457cae1be07c0f0ca90a63931200a47d9b6a6af84fd7b45f"}, + {file = "filelock-3.13.4.tar.gz", hash = "sha256:d13f466618bfde72bd2c18255e269f72542c6e70e7bac83a0232d6b1cc5c8cf4"}, ] [package.extras] -docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] +docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] typing = ["typing-extensions (>=4.8)"] [[package]] @@ -694,13 +694,13 @@ sphinx-basic-ng = "*" [[package]] name = "identify" -version = "2.5.33" +version = "2.5.36" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.33-py2.py3-none-any.whl", hash = "sha256:d40ce5fcd762817627670da8a7d8d8e65f24342d14539c59488dc603bf662e34"}, - {file = "identify-2.5.33.tar.gz", hash = "sha256:161558f9fe4559e1557e1bff323e8631f6a0e4837f7497767c1782832f16b62d"}, + {file = "identify-2.5.36-py2.py3-none-any.whl", hash = "sha256:37d93f380f4de590500d9dba7db359d0d3da95ffe7f9de1753faa159e71e7dfa"}, + {file = "identify-2.5.36.tar.gz", hash = "sha256:e5e00f54165f9047fbebeb4a560f9acfb8af4c88232be60a488e9b68d122745d"}, ] [package.extras] @@ -708,13 +708,13 @@ license = ["ukkonen"] [[package]] name = "idna" -version = "3.6" +version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, - {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] [[package]] @@ -730,22 +730,22 @@ files = [ [[package]] name = "importlib-metadata" -version = "7.0.1" +version = "7.1.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_metadata-7.0.1-py3-none-any.whl", hash = "sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e"}, - {file = "importlib_metadata-7.0.1.tar.gz", hash = "sha256:f238736bb06590ae52ac1fab06a3a9ef1d8dce2b7a35b5ab329371d6c8f5d2cc"}, + {file = "importlib_metadata-7.1.0-py3-none-any.whl", hash = "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570"}, + {file = "importlib_metadata-7.1.0.tar.gz", hash = "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2"}, ] [package.dependencies] zipp = ">=0.5" [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] [[package]] name = "jinja2" @@ -1028,39 +1028,40 @@ setuptools = "*" [[package]] name = "packaging" -version = "23.2" +version = "24.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, ] [[package]] name = "platformdirs" -version = "4.2.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +version = "4.2.1" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, - {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, + {file = "platformdirs-4.2.1-py3-none-any.whl", hash = "sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1"}, + {file = "platformdirs-4.2.1.tar.gz", hash = "sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf"}, ] [package.extras] docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] [[package]] name = "pre-commit" -version = "3.6.0" +version = "3.7.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" files = [ - {file = "pre_commit-3.6.0-py2.py3-none-any.whl", hash = "sha256:c255039ef399049a5544b6ce13d135caba8f2c28c3b4033277a788f434308376"}, - {file = "pre_commit-3.6.0.tar.gz", hash = "sha256:d30bad9abf165f7785c15a21a1f46da7d0677cb00ee7ff4c579fd38922efe15d"}, + {file = "pre_commit-3.7.0-py2.py3-none-any.whl", hash = "sha256:5eae9e10c2b5ac51577c3452ec0a490455c45a0533f7960f993a0d01e59decab"}, + {file = "pre_commit-3.7.0.tar.gz", hash = "sha256:e209d61b8acdcf742404408531f0c37d49d2c734fd7cff2d6076083d191cb060"}, ] [package.dependencies] @@ -1072,13 +1073,13 @@ virtualenv = ">=20.10.0" [[package]] name = "pycparser" -version = "2.21" +version = "2.22" description = "C parser in Python" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.8" files = [ - {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, - {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, ] [[package]] @@ -1172,13 +1173,13 @@ pyobjc-core = ">=9.2" [[package]] name = "pyright" -version = "1.1.350" +version = "1.1.359" description = "Command line wrapper for pyright" optional = false python-versions = ">=3.7" files = [ - {file = "pyright-1.1.350-py3-none-any.whl", hash = "sha256:f1dde6bcefd3c90aedbe9dd1c573e4c1ddbca8c74bf4fa664dd3b1a599ac9a66"}, - {file = "pyright-1.1.350.tar.gz", hash = "sha256:a8ba676de3a3737ea4d8590604da548d4498cc5ee9ee00b1a403c6db987916c6"}, + {file = "pyright-1.1.359-py3-none-any.whl", hash = "sha256:5582777be7eab73512277ac7da7b41e15bc0737f488629cb9babd96e0769be61"}, + {file = "pyright-1.1.359.tar.gz", hash = "sha256:f0eab50f3dafce8a7302caeafd6a733f39901a2bf5170bb23d77fd607c8a8dbc"}, ] [package.dependencies] @@ -1271,19 +1272,19 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "setuptools" -version = "69.0.3" +version = "69.5.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-69.0.3-py3-none-any.whl", hash = "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05"}, - {file = "setuptools-69.0.3.tar.gz", hash = "sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78"}, + {file = "setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32"}, + {file = "setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "six" @@ -1320,20 +1321,20 @@ files = [ [[package]] name = "sphinx" -version = "7.2.6" +version = "7.3.7" description = "Python documentation generator" optional = false python-versions = ">=3.9" files = [ - {file = "sphinx-7.2.6-py3-none-any.whl", hash = "sha256:1e09160a40b956dc623c910118fa636da93bd3ca0b9876a7b3df90f07d691560"}, - {file = "sphinx-7.2.6.tar.gz", hash = "sha256:9a5160e1ea90688d5963ba09a2dcd8bdd526620edbb65c328728f1b2228d5ab5"}, + {file = "sphinx-7.3.7-py3-none-any.whl", hash = "sha256:413f75440be4cacf328f580b4274ada4565fb2187d696a84970c23f77b64d8c3"}, + {file = "sphinx-7.3.7.tar.gz", hash = "sha256:a4a7db75ed37531c05002d56ed6948d4c42f473a36f46e1382b0bd76ca9627bc"}, ] [package.dependencies] -alabaster = ">=0.7,<0.8" +alabaster = ">=0.7.14,<0.8.0" babel = ">=2.9" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.18.1,<0.21" +docutils = ">=0.18.1,<0.22" imagesize = ">=1.3" importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} Jinja2 = ">=3.0" @@ -1347,11 +1348,12 @@ sphinxcontrib-htmlhelp = ">=2.0.0" sphinxcontrib-jsmath = "*" sphinxcontrib-qthelp = "*" sphinxcontrib-serializinghtml = ">=1.1.9" +tomli = {version = ">=2", markers = "python_version < \"3.11\""} [package.extras] docs = ["sphinxcontrib-websupport"] -lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-simplify", "isort", "mypy (>=0.990)", "ruff", "sphinx-lint", "types-requests"] -test = ["cython (>=3.0)", "filelock", "html5lib", "pytest (>=4.6)", "setuptools (>=67.0)"] +lint = ["flake8 (>=3.5.0)", "importlib_metadata", "mypy (==1.9.0)", "pytest (>=6.0)", "ruff (==0.3.7)", "sphinx-lint", "tomli", "types-docutils", "types-requests"] +test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=6.0)", "setuptools (>=67.0)"] [[package]] name = "sphinx-autoapi" @@ -1502,26 +1504,37 @@ files = [ [package.dependencies] six = "*" +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + [[package]] name = "typing-extensions" -version = "4.9.0" +version = "4.11.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, - {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, + {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, + {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, ] [[package]] name = "urllib3" -version = "2.2.0" +version = "2.2.1" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.2.0-py3-none-any.whl", hash = "sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224"}, - {file = "urllib3-2.2.0.tar.gz", hash = "sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20"}, + {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, + {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, ] [package.extras] @@ -1532,13 +1545,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.25.0" +version = "20.26.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.25.0-py3-none-any.whl", hash = "sha256:4238949c5ffe6876362d9c0180fc6c3a824a7b12b80604eeb8085f2ed7460de3"}, - {file = "virtualenv-20.25.0.tar.gz", hash = "sha256:bf51c0d9c7dd63ea8e44086fa1e4fb1093a31e963b86959257378aef020e1f1b"}, + {file = "virtualenv-20.26.0-py3-none-any.whl", hash = "sha256:0846377ea76e818daaa3e00a4365c018bc3ac9760cbb3544de542885aad61fb3"}, + {file = "virtualenv-20.26.0.tar.gz", hash = "sha256:ec25a9671a5102c8d2657f62792a27b48f016664c6873f6beed3800008577210"}, ] [package.dependencies] @@ -1547,7 +1560,7 @@ filelock = ">=3.12.2,<4" platformdirs = ">=3.9.1,<5" [package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [[package]] @@ -1873,18 +1886,18 @@ multidict = ">=4.0" [[package]] name = "zipp" -version = "3.17.0" +version = "3.18.1" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" files = [ - {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, - {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, + {file = "zipp-3.18.1-py3-none-any.whl", hash = "sha256:206f5a15f2af3dbaee80769fb7dc6f249695e940acca08dfb2a4769fe61e538b"}, + {file = "zipp-3.18.1.tar.gz", hash = "sha256:2884ed22e7d8961de1c9a05142eb69a247f120291bc0206a00a7642f09b5b715"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [extras] scan = ["bleak"] @@ -1892,4 +1905,4 @@ scan = ["bleak"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.13" -content-hash = "ac0ef4ca30b86c4ef5b45db550b69e6ef85203e9c667cc5002ae8998cd05edd6" +content-hash = "828fc3307e8314148461691a7ef95572699b2e9597713a118c469a5532c65d61" diff --git a/pyproject.toml b/pyproject.toml index 773b4b1..c7a5aad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "FindMy" -version = "0.4.0" +version = "0.5.0" description = "Everything you need to work with Apple's Find My network!" authors = ["Mike Almeloo "] readme = "README.md" @@ -9,7 +9,7 @@ packages = [{ include = "findmy" }] [tool.poetry.dependencies] python = ">=3.9,<3.13" srp = "^1.0.20" -cryptography = "^42.0.2" +cryptography = "^42.0.5" beautifulsoup4 = "^4.12.2" aiohttp = "^3.9.1" bleak = "^0.21.1" @@ -51,6 +51,7 @@ ignore = [ "D105", # docstrings in magic methods "PLR2004", # "magic" values >.> + "FBT", # boolean "traps" ] line-length = 100