From 5dbbeb3394f80aa580624dc7fce60b339859b473 Mon Sep 17 00:00:00 2001 From: William Bruno Date: Sat, 4 Apr 2026 22:30:27 -0400 Subject: [PATCH] bump pymd3 --- __pycache__/main.cpython-314.pyc | Bin 4012 -> 4065 bytes pyproject.toml | 2 +- src/pymd3_vue_location_sim/server.py | 195 +++++++++++++++++---------- uv.lock | 52 ++----- 4 files changed, 137 insertions(+), 112 deletions(-) diff --git a/__pycache__/main.cpython-314.pyc b/__pycache__/main.cpython-314.pyc index d96cacc25bbb3ec583c25ec964fba4a0f8b32008..7147ec652486ed79348d369c8a01db1576a46578 100644 GIT binary patch delta 295 zcmZ1@|4^P+n~#@^0SF$PJe27@kyn!O!$kG?dU=L8h9C}4eh+~X0eOZXPH~13!C3A% z1|2DoS_lC08Ik!xT(Rr{Dll0D9mEZRv3!aQCQxBx&LAFfh9F*Xh8%$$#uDLJksv;c z2nL1}pj%`ZV#Ng!N`clhDub~m|6~!yR7R%BRgBt9nv9c|GWs$7-#_^)W2Gn$(A;7W z(ZFzrg?~cig3#+M+MAo1qF5REH{WB|1W_EkQjCnEla=|7G2POfypKlyu+9ONheXRH+E2AWyS10)(4?y&Grh+GhIokeSNFH;mNBj4s1 q?3y4-h*ye{QDm|%-?7PZ{5FhklcV`HIO8ue#D8L81e(^v9|`~sX)Fl< diff --git a/pyproject.toml b/pyproject.toml index 14c8109..c5a2b73 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ dependencies = [ "numpy==2.4.3", "pydantic==2.12.5", "pyicloud>=2.4.1", - "pymobiledevice3==9.0.0", + "pymobiledevice3==9.8.1", "python-dotenv>=1.2.2", "python-socketio==5.16.1", "sqlalchemy>=2.0.48", diff --git a/src/pymd3_vue_location_sim/server.py b/src/pymd3_vue_location_sim/server.py index a5793b6..2390f8f 100644 --- a/src/pymd3_vue_location_sim/server.py +++ b/src/pymd3_vue_location_sim/server.py @@ -11,16 +11,17 @@ import random import math import socketio import httpx -from contextlib import asynccontextmanager, suppress +from contextlib import suppress from typing import Optional, Dict from dotenv import load_dotenv with warnings.catch_warnings(): # Ignore: "Core Pydantic V1 functionality isn't compatible with Python 3.14 or greater." warnings.simplefilter("ignore", category=UserWarning) + warnings.simplefilter("ignore", category=UserWarning) import fastapi import uvicorn -from fastapi import FastAPI, APIRouter, Request +from fastapi import FastAPI, APIRouter, Request, Response from fastapi.encoders import jsonable_encoder from fastapi.staticfiles import StaticFiles from fastapi.responses import FileResponse @@ -140,16 +141,6 @@ class TunneldRunnerSio: usbmux_monitor: bool = True, mobdev2_monitor: bool = True, ) -> None: - # instance = cls( - # host, - # port, - # protocol=protocol, - # usb_monitor=usb_monitor, - # wifi_monitor=wifi_monitor, - # usbmux_monitor=usbmux_monitor, - # mobdev2_monitor=mobdev2_monitor, - # context=context,) - # asyncio.run(instance._run_app()) cls( host, port, @@ -158,7 +149,8 @@ class TunneldRunnerSio: wifi_monitor=wifi_monitor, usbmux_monitor=usbmux_monitor, mobdev2_monitor=mobdev2_monitor, - context=context)._run_app() + context=context, + )._run_app() def __init__( self, @@ -171,11 +163,12 @@ class TunneldRunnerSio: usbmux_monitor: bool = True, mobdev2_monitor: bool = True, ): - @asynccontextmanager - async def lifespan(app: FastAPI): + async def app_startup() -> None: + logger.info("Application startup: starting tunneld core and watcher") self._tunneld_core.start() await start_tunnel_watcher() - yield + + async def app_shutdown() -> None: logger.info("Closing tunneld tasks...") await end_tunnel_watcher() await end_icloud_monitor() @@ -195,10 +188,14 @@ class TunneldRunnerSio: self._vue_dist = os.getenv("VUE_DIST") self._vue_app = FastAPI( title="iOS Device Management API", - lifespan=lifespan, cors_allowed_origins="*", ) - self._asgi_app = socketio.ASGIApp(self.context.sio, self._vue_app) + self._asgi_app = socketio.ASGIApp( + self.context.sio, + self._vue_app, + on_startup=app_startup, + on_shutdown=app_shutdown, + ) self.context.icloud_monitor.sio = self.context.sio self.context.icloud_monitor.get_client_sids = lambda: list( self.context.connected_clients @@ -347,60 +344,84 @@ class TunneldRunnerSio: async def tunnel_watcher_loop() -> None: previous = collect_active_tunnels() while True: - await asyncio.sleep(1) - current = collect_active_tunnels() - added_keys = set(current.keys()) - set(previous.keys()) - removed_keys = set(previous.keys()) - set(current.keys()) - added = [current[k] for k in sorted(added_keys)] - removed = [previous[k] for k in sorted(removed_keys)] - for item in added: - logger.info( - "Tunnel discovered interface=%s udid=%s address=%s port=%s transport=%s", - item.get("interface"), - item.get("udid"), - item.get("address"), - item.get("port"), - item.get("transport"), - ) - await safe_sio_emit("tunnel_device_connected", item) - logger.info(f"Current udid: %s, new udid: %s", - self.context.udid, item.get("udid")) - if self.context.udid is None: - self.context.udid = item.get("udid") - device_name = await get_device_name() - if device_name != self.context.device_name: - self._tunneld_core.cancel(udid=item.get("udid")) - logger.warning( - "Tunnel established to wrong device. Dropping tunnel. wrong_udid=%s for device: %s", - self.context.udid, self.context.device_name) - self.context.udid = None - await safe_sio_emit( - "appError", - { - "type": "tunnel_wrong_device", - "message": "Tunnel established to wrong device. Dropping tunnel.", - "wrong_udid": self.context.udid, - "device_name": device_name, - }, - ) - await get_tun(item.get("udid"), max_retries=10, retry_delay=1.0) + try: + await asyncio.sleep(1) + current = collect_active_tunnels() + added_keys = set(current.keys()) - set(previous.keys()) + removed_keys = set(previous.keys()) - set(current.keys()) + added = [current[k] for k in sorted(added_keys)] + removed = [previous[k] for k in sorted(removed_keys)] + for item in added: + try: + logger.info( + "Tunnel discovered interface=%s udid=%s address=%s port=%s transport=%s", + item.get("interface"), + item.get("udid"), + item.get("address"), + item.get("port"), + item.get("transport"), + ) + await safe_sio_emit("tunnel_device_connected", item) + logger.info(f"Current udid: %s, new udid: %s", + self.context.udid, item.get("udid")) + if self.context.udid is None: + self.context.udid = item.get("udid") + device_name = await get_device_name() + selected_device_name = self.context.device_name + if selected_device_name and device_name != selected_device_name: + wrong_udid = self.context.udid + self._tunneld_core.cancel(udid=item.get("udid")) + logger.warning( + "Tunnel established to wrong device. Dropping tunnel. wrong_udid=%s for device: %s", + wrong_udid, selected_device_name) + self.context.udid = None + await safe_sio_emit( + "appError", + { + "type": "tunnel_wrong_device", + "message": "Tunnel established to wrong device. Dropping tunnel.", + "wrong_udid": wrong_udid, + "device_name": device_name, + }, + ) + await get_tun(item.get("udid"), max_retries=10, retry_delay=1.0) + except Exception: + logger.exception( + "Tunnel watcher failed while handling added tunnel for udid=%s", + item.get("udid"), + ) - if removed: - for item in removed: - logger.warning( - "Tunnel disconnected interface=%s udid=%s address=%s port=%s", - item.get("interface"), - item.get("udid"), - item.get("address"), - item.get("port"), - ) - await safe_sio_emit("tunnel_device_disconnected", item) - await handle_tunnel_drop(removed) + if removed: + for item in removed: + try: + logger.warning( + "Tunnel disconnected interface=%s udid=%s address=%s port=%s", + item.get("interface"), + item.get("udid"), + item.get("address"), + item.get("port"), + ) + await safe_sio_emit("tunnel_device_disconnected", item) + except Exception: + logger.exception( + "Tunnel watcher failed while emitting disconnection for udid=%s", + item.get("udid"), + ) + await handle_tunnel_drop(removed) - previous = current + previous = current + except asyncio.CancelledError: + raise + except Exception: + logger.exception("Tunnel watcher loop crashed; continuing") async def start_tunnel_watcher() -> None: if self.context.tunnel_watcher_task is None or self.context.tunnel_watcher_task.done(): + if self.context.tunnel_watcher_task is not None and self.context.tunnel_watcher_task.done(): + with suppress(Exception): + exc = self.context.tunnel_watcher_task.exception() + if exc is not None: + logger.error("Previous tunnel watcher task exited with error: %s", exc) self.context.tunnel_watcher_task = asyncio.create_task( tunnel_watcher_loop(), name="tunnel-task-watcher", @@ -1303,17 +1324,49 @@ class TunneldRunnerSio: logger.info("Reverse Geocoded %s to %s", coords, rev_geocode) return generate_http_response(rev_geocode) - @self._app.post("/proxy/ors/{full_path:path}") + @self._app.get("/ors/status") + async def app_ors_status(request: Request) -> fastapi.Response: + logger.info("request: %s", request) + target_url = "https://ors.intrepidnet.org/ors/v2/status" + async with httpx.AsyncClient() as client: + # Forward the request to the external service + response = await client.get(target_url, params=request.query_params) + # Return the response content and status code back to the original client + return Response( + content=response.content, + status_code=response.status_code, + headers=dict(response.headers) + ) + + @self._app.get("/ors/health") + async def app_ors_status(request: Request) -> fastapi.Response: + logger.info("request: %s", request) + target_url = "https://ors.intrepidnet.org/ors/v2/health" + async with httpx.AsyncClient() as client: + # Forward the request to the external service + response = await client.get(target_url, params=request.query_params) + # Return the response content and status code back to the original client + return Response( + content=response.content, + status_code=response.status_code, + headers=dict(response.headers) + ) + + @self._app.post("/ors/proxy/{full_path:path}") async def app_proxy_ors(full_path: str, request: Request): logger.info("request: %s", request) - body = await request.body() + body = await request.json() headers = dict(request.headers) method = request.method url = "https://ors.intrepidnet.org/" + full_path + logger.info("body: %s", body) + logger.info("headers: %s", headers) + logger.info("method: %s", method) + logger.info("url: %s", url) async with httpx.AsyncClient() as client: - response = await client.request(method, url, headers=headers, content=body) - return response.content + response = await client.request(method, url, json=body) + return response.json() """ Socket.IO Functions""" diff --git a/uv.lock b/uv.lock index afbe606..b0d237e 100644 --- a/uv.lock +++ b/uv.lock @@ -688,21 +688,20 @@ wheels = [ [[package]] name = "ipsw-parser" -version = "1.5.0" +version = "1.6.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cached-property" }, - { name = "click" }, { name = "coloredlogs" }, { name = "construct" }, { name = "plumbum" }, { name = "pyimg4" }, { name = "remotezip2" }, { name = "requests" }, + { name = "typer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/96/c820ec1e2f50d398c2442b2f8759448a896a300745c2920c4cf7ba89dd4c/ipsw_parser-1.5.0.tar.gz", hash = "sha256:5becd2000017b7b8549cc6b6e3f5149541792bf6353663d0f77932965bf26aad", size = 51957, upload-time = "2025-11-18T21:57:37.881Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/8a/bae825b78838c86b05bd7ca3bf26e98dfcf3d0955619fe717402a628dfa7/ipsw_parser-1.6.0.tar.gz", hash = "sha256:d3121ae9f2a12bae6604972a9e00f6168cdedb3ad3db6b178061b4995e8fd14f", size = 53471, upload-time = "2026-03-17T20:27:27.524Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/99/b5805555d14abf34d9534c9e8ba81d629107fa5d3bd7f110ca752a21d1c2/ipsw_parser-1.5.0-py3-none-any.whl", hash = "sha256:740248a1937d7b7dacc5d98e758b578ed88c357638126a8a71d4b0c100a3c9eb", size = 36611, upload-time = "2025-11-18T21:57:36.975Z" }, + { url = "https://files.pythonhosted.org/packages/a8/a3/9671c80252fc7a8b870b01639c3ae821d39f9489ac346bce7d8bfbf168f4/ipsw_parser-1.6.0-py3-none-any.whl", hash = "sha256:0e1f2b726c053617c65b0cef04afeecdf73ad14cc17e7e58d6382209d6693466", size = 37623, upload-time = "2026-03-17T20:27:26.518Z" }, ] [[package]] @@ -834,21 +833,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4a/0d/9c59313ab43d0858a9a665e80763bd830dc78d5f379afc3815e123c486c2/keyrings.alt-5.0.2-py3-none-any.whl", hash = "sha256:6be74693192f3f37bbb752bfac9b86e6177076b17d2ac12a390f1d6abff8ac7c", size = 17930, upload-time = "2024-08-14T01:09:26.785Z" }, ] -[[package]] -name = "la-panic" -version = "0.5.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cached-property" }, - { name = "click" }, - { name = "coloredlogs" }, - { name = "setuptools" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/85/28/757e1ccd939162caa27c8a6173d490deb986c38a7fd73fe2f264f6d7485d/la-panic-0.5.0.tar.gz", hash = "sha256:5239025d1e96aaed1fbd1c4a5d35572fd70cf42ddd68839ff1e4f1d21e3e279b", size = 57320, upload-time = "2023-11-12T17:49:33.159Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/63/8a/59eb5256cb0449c6f052453a1ce1fc32eed77ebd93b6be43f0136a7428a8/la_panic-0.5.0-py3-none-any.whl", hash = "sha256:5e224e5d038a020897606baf36e3aecbd33a5e56d82d0ac9cd72bcc0d5c3007d", size = 51866, upload-time = "2023-11-12T17:49:31.277Z" }, -] - [[package]] name = "loguru" version = "0.7.3" @@ -1191,16 +1175,14 @@ wheels = [ [[package]] name = "pycrashreport" -version = "1.2.7" +version = "2.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cached-property" }, - { name = "click" }, - { name = "la-panic" }, + { name = "typer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d5/0b/534e02c6badef117b5a4054c065b50e6055d833b983b024d57afb425b618/pycrashreport-1.2.7.tar.gz", hash = "sha256:8f4d52d3292c1ec479fac589633d1477d19fbeb6ab01960969389c6a9c63ed7f", size = 100834, upload-time = "2025-08-25T07:15:42.872Z" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/9c/1e1c2d84f746972cbe4d5e65f816008c64e427fc680f55115aa313683c23/pycrashreport-2.0.0.tar.gz", hash = "sha256:31d5e32faa3a047fe01e923bde3eaf1ec86b23e264babf2fc8f7b61fe4812342", size = 102682, upload-time = "2026-03-30T22:35:19.705Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/09/ac/751f4e55f5f3de5f152a5891682da3c25a8c57c7058a2274ca5a430a9fe2/pycrashreport-1.2.7-py3-none-any.whl", hash = "sha256:70a448ec44b86b016ce81346552d32400cc2c51d7e1928420e98a37461319866", size = 33056, upload-time = "2025-08-25T07:15:41.323Z" }, + { url = "https://files.pythonhosted.org/packages/ed/ec/be636536ab13a63e3385a82c841d08ce2a51712ffc2a337cd24afc12e038/pycrashreport-2.0.0-py3-none-any.whl", hash = "sha256:12feb922c3349cdb33b468d105915f5f829c53df25640c5c2241bc4182bdb422", size = 34244, upload-time = "2026-03-30T22:35:18.478Z" }, ] [[package]] @@ -1387,7 +1369,7 @@ requires-dist = [ { name = "numpy", specifier = "==2.4.3" }, { name = "pydantic", specifier = "==2.12.5" }, { name = "pyicloud", specifier = ">=2.4.1" }, - { name = "pymobiledevice3", specifier = "==9.0.0" }, + { name = "pymobiledevice3", specifier = "==9.8.1" }, { name = "python-dotenv", specifier = ">=1.2.2" }, { name = "python-socketio", specifier = "==5.16.1" }, { name = "sqlalchemy", specifier = ">=2.0.48" }, @@ -1399,12 +1381,11 @@ requires-dist = [ [[package]] name = "pymobiledevice3" -version = "9.0.0" +version = "9.8.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "asn1" }, { name = "bpylist2" }, - { name = "click" }, { name = "coloredlogs" }, { name = "construct" }, { name = "construct-typing" }, @@ -1445,9 +1426,9 @@ dependencies = [ { name = "wsproto" }, { name = "xonsh" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1d/89/aed5abbb6a4ece29bed2b9e85ccfbc28a8197658a4923e8cd2d5193796d8/pymobiledevice3-9.0.0.tar.gz", hash = "sha256:e85c169d67cf17d1dcf4ce26e3a84a801e86d13a0144ff7fb57eb532745ddcfb", size = 735101, upload-time = "2026-03-11T08:37:05.172Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/0e/81b8c391929cb2e2c9c831968df5c9cabc5687ab5d45fc9c345fcdb48171/pymobiledevice3-9.8.1.tar.gz", hash = "sha256:621479ad545795e52af1955e0cc6c8e807b51d81442173b3386f9d1fef21308a", size = 758015, upload-time = "2026-03-31T15:00:55.143Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/ae/aaff1375b383c78729b6b25abac1458e9fd5c0c84b8da364bf204559dc16/pymobiledevice3-9.0.0-py3-none-any.whl", hash = "sha256:7366533cc8807299ef0b88c6c56a77120f7d984914ec6436191a7cc2991d3ae7", size = 789609, upload-time = "2026-03-11T08:37:01.731Z" }, + { url = "https://files.pythonhosted.org/packages/1a/58/3275db73730e99a6345231a7deeef5b184bd6a26b5dd40adefcb5ad0f84d/pymobiledevice3-9.8.1-py3-none-any.whl", hash = "sha256:9e1a5c8277a8a7955544579f118da8645f83ac05a1a03e79a96307242089842a", size = 797514, upload-time = "2026-03-31T15:00:53.169Z" }, ] [[package]] @@ -1661,15 +1642,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" }, ] -[[package]] -name = "setuptools" -version = "82.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/f3/748f4d6f65d1756b9ae577f329c951cda23fb900e4de9f70900ced962085/setuptools-82.0.0.tar.gz", hash = "sha256:22e0a2d69474c6ae4feb01951cb69d515ed23728cf96d05513d36e42b62b37cb", size = 1144893, upload-time = "2026-02-08T15:08:40.206Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/c6/76dc613121b793286a3f91621d7b75a2b493e0390ddca50f11993eadf192/setuptools-82.0.0-py3-none-any.whl", hash = "sha256:70b18734b607bd1da571d097d236cfcfacaf01de45717d59e6e04b96877532e0", size = 1003468, upload-time = "2026-02-08T15:08:38.723Z" }, -] - [[package]] name = "shellingham" version = "1.5.4"