From 0ce7c427963858645fbff459a901292cc288883f Mon Sep 17 00:00:00 2001 From: Asher Norland Date: Sun, 22 Mar 2026 21:28:58 -0700 Subject: [PATCH] WSL Distro Interface --- pdm.lock | 377 ++++++++++++---- pyproject.toml | 4 +- .../application/screen/action_card.py | 107 ++++- synodic_client/application/screen/screen.py | 20 + synodic_client/application/screen/wsl.py | 419 ++++++++++++++++++ synodic_client/application/theme.py | 16 + synodic_client/operations/install.py | 1 + synodic_client/operations/schema.py | 2 + synodic_client/updater.py | 7 +- 9 files changed, 847 insertions(+), 106 deletions(-) create mode 100644 synodic_client/application/screen/wsl.py diff --git a/pdm.lock b/pdm.lock index a8eef4f..5c90775 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,11 +5,91 @@ groups = ["default", "build", "lint", "test"] strategy = ["inherit_metadata"] lock_version = "4.5.0" -content_hash = "sha256:8be743eaadbd30a791172a10d1a19f2cfd3ec16037ed1b4c219483a414a70f4f" +content_hash = "sha256:fbc1bb821435f99f3181eaefe90bc228dfc13d4cd61c750bc96a47a8c2045217" [[metadata.targets]] requires_python = ">=3.14,<3.15" +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +requires_python = ">=3.9" +summary = "Happy Eyeballs for asyncio" +groups = ["default"] +files = [ + {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, + {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, +] + +[[package]] +name = "aiohttp" +version = "3.13.3" +requires_python = ">=3.9" +summary = "Async http client/server framework (asyncio)" +groups = ["default"] +dependencies = [ + "aiohappyeyeballs>=2.5.0", + "aiosignal>=1.4.0", + "async-timeout<6.0,>=4.0; python_version < \"3.11\"", + "attrs>=17.3.0", + "frozenlist>=1.1.1", + "multidict<7.0,>=4.5", + "propcache>=0.2.0", + "yarl<2.0,>=1.17.0", +] +files = [ + {file = "aiohttp-3.13.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:ea37047c6b367fd4bd632bff8077449b8fa034b69e812a18e0132a00fae6e808"}, + {file = "aiohttp-3.13.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6fc0e2337d1a4c3e6acafda6a78a39d4c14caea625124817420abceed36e2415"}, + {file = "aiohttp-3.13.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c685f2d80bb67ca8c3837823ad76196b3694b0159d232206d1e461d3d434666f"}, + {file = "aiohttp-3.13.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48e377758516d262bde50c2584fc6c578af272559c409eecbdd2bae1601184d6"}, + {file = "aiohttp-3.13.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:34749271508078b261c4abb1767d42b8d0c0cc9449c73a4df494777dc55f0687"}, + {file = "aiohttp-3.13.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:82611aeec80eb144416956ec85b6ca45a64d76429c1ed46ae1b5f86c6e0c9a26"}, + {file = "aiohttp-3.13.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2fff83cfc93f18f215896e3a190e8e5cb413ce01553901aca925176e7568963a"}, + {file = "aiohttp-3.13.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bbe7d4cecacb439e2e2a8a1a7b935c25b812af7a5fd26503a66dadf428e79ec1"}, + {file = "aiohttp-3.13.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b928f30fe49574253644b1ca44b1b8adbd903aa0da4b9054a6c20fc7f4092a25"}, + {file = "aiohttp-3.13.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7b5e8fe4de30df199155baaf64f2fcd604f4c678ed20910db8e2c66dc4b11603"}, + {file = "aiohttp-3.13.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:8542f41a62bcc58fc7f11cf7c90e0ec324ce44950003feb70640fc2a9092c32a"}, + {file = "aiohttp-3.13.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5e1d8c8b8f1d91cd08d8f4a3c2b067bfca6ec043d3ff36de0f3a715feeedf926"}, + {file = "aiohttp-3.13.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:90455115e5da1c3c51ab619ac57f877da8fd6d73c05aacd125c5ae9819582aba"}, + {file = "aiohttp-3.13.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:042e9e0bcb5fba81886c8b4fbb9a09d6b8a00245fd8d88e4d989c1f96c74164c"}, + {file = "aiohttp-3.13.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2eb752b102b12a76ca02dff751a801f028b4ffbbc478840b473597fc91a9ed43"}, + {file = "aiohttp-3.13.3-cp314-cp314-win32.whl", hash = "sha256:b556c85915d8efaed322bf1bdae9486aa0f3f764195a0fb6ee962e5c71ef5ce1"}, + {file = "aiohttp-3.13.3-cp314-cp314-win_amd64.whl", hash = "sha256:9bf9f7a65e7aa20dd764151fb3d616c81088f91f8df39c3893a536e279b4b984"}, + {file = "aiohttp-3.13.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:05861afbbec40650d8a07ea324367cb93e9e8cc7762e04dd4405df99fa65159c"}, + {file = "aiohttp-3.13.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2fc82186fadc4a8316768d61f3722c230e2c1dcab4200d52d2ebdf2482e47592"}, + {file = "aiohttp-3.13.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0add0900ff220d1d5c5ebbf99ed88b0c1bbf87aa7e4262300ed1376a6b13414f"}, + {file = "aiohttp-3.13.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:568f416a4072fbfae453dcf9a99194bbb8bdeab718e08ee13dfa2ba0e4bebf29"}, + {file = "aiohttp-3.13.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:add1da70de90a2569c5e15249ff76a631ccacfe198375eead4aadf3b8dc849dc"}, + {file = "aiohttp-3.13.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:10b47b7ba335d2e9b1239fa571131a87e2d8ec96b333e68b2a305e7a98b0bae2"}, + {file = "aiohttp-3.13.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3dd4dce1c718e38081c8f35f323209d4c1df7d4db4bab1b5c88a6b4d12b74587"}, + {file = "aiohttp-3.13.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34bac00a67a812570d4a460447e1e9e06fae622946955f939051e7cc895cfab8"}, + {file = "aiohttp-3.13.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a19884d2ee70b06d9204b2727a7b9f983d0c684c650254679e716b0b77920632"}, + {file = "aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5f8ca7f2bb6ba8348a3614c7918cc4bb73268c5ac2a207576b7afea19d3d9f64"}, + {file = "aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:b0d95340658b9d2f11d9697f59b3814a9d3bb4b7a7c20b131df4bcef464037c0"}, + {file = "aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:a1e53262fd202e4b40b70c3aff944a8155059beedc8a89bba9dc1f9ef06a1b56"}, + {file = "aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:d60ac9663f44168038586cab2157e122e46bdef09e9368b37f2d82d354c23f72"}, + {file = "aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:90751b8eed69435bac9ff4e3d2f6b3af1f57e37ecb0fbeee59c0174c9e2d41df"}, + {file = "aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fc353029f176fd2b3ec6cfc71be166aba1936fe5d73dd1992ce289ca6647a9aa"}, + {file = "aiohttp-3.13.3-cp314-cp314t-win32.whl", hash = "sha256:2e41b18a58da1e474a057b3d35248d8320029f61d70a37629535b16a0c8f3767"}, + {file = "aiohttp-3.13.3-cp314-cp314t-win_amd64.whl", hash = "sha256:44531a36aa2264a1860089ffd4dce7baf875ee5a6079d5fb42e261c704ef7344"}, + {file = "aiohttp-3.13.3.tar.gz", hash = "sha256:a949eee43d3782f2daae4f4a2819b2cb9b0c5d3b7f7a927067cc84dafdbb9f88"}, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +requires_python = ">=3.9" +summary = "aiosignal: a list of registered asynchronous callbacks" +groups = ["default"] +dependencies = [ + "frozenlist>=1.1.0", + "typing-extensions>=4.2; python_version < \"3.13\"", +] +files = [ + {file = "aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e"}, + {file = "aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7"}, +] + [[package]] name = "altgraph" version = "0.17.5" @@ -46,30 +126,14 @@ files = [ ] [[package]] -name = "anyio" -version = "4.12.1" +name = "attrs" +version = "26.1.0" requires_python = ">=3.9" -summary = "High-level concurrency and networking framework on top of asyncio or Trio" -groups = ["default"] -dependencies = [ - "exceptiongroup>=1.0.2; python_version < \"3.11\"", - "idna>=2.8", - "typing-extensions>=4.5; python_version < \"3.13\"", -] -files = [ - {file = "anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c"}, - {file = "anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703"}, -] - -[[package]] -name = "certifi" -version = "2026.2.25" -requires_python = ">=3.7" -summary = "Python package for providing Mozilla's CA Bundle." +summary = "Classes Without Boilerplate" groups = ["default"] files = [ - {file = "certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa"}, - {file = "certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7"}, + {file = "attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309"}, + {file = "attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32"}, ] [[package]] @@ -186,46 +250,46 @@ files = [ ] [[package]] -name = "h11" -version = "0.16.0" -requires_python = ">=3.8" -summary = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -groups = ["default"] -files = [ - {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, - {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, -] - -[[package]] -name = "httpcore" -version = "1.0.9" -requires_python = ">=3.8" -summary = "A minimal low-level HTTP client." -groups = ["default"] -dependencies = [ - "certifi", - "h11>=0.16", -] -files = [ - {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, - {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, -] - -[[package]] -name = "httpx" -version = "0.28.1" -requires_python = ">=3.8" -summary = "The next generation HTTP client." +name = "frozenlist" +version = "1.8.0" +requires_python = ">=3.9" +summary = "A list-like structure which implements collections.abc.MutableSequence" groups = ["default"] -dependencies = [ - "anyio", - "certifi", - "httpcore==1.*", - "idna", -] files = [ - {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, - {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, + {file = "frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0"}, + {file = "frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f"}, + {file = "frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c"}, + {file = "frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2"}, + {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8"}, + {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686"}, + {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e"}, + {file = "frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a"}, + {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128"}, + {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f"}, + {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7"}, + {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30"}, + {file = "frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7"}, + {file = "frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806"}, + {file = "frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0"}, + {file = "frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b"}, + {file = "frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d"}, + {file = "frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed"}, + {file = "frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930"}, + {file = "frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c"}, + {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24"}, + {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37"}, + {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a"}, + {file = "frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2"}, + {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef"}, + {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe"}, + {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8"}, + {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a"}, + {file = "frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e"}, + {file = "frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df"}, + {file = "frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd"}, + {file = "frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79"}, + {file = "frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d"}, + {file = "frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad"}, ] [[package]] @@ -289,6 +353,56 @@ files = [ {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] +[[package]] +name = "multidict" +version = "6.7.1" +requires_python = ">=3.9" +summary = "multidict implementation" +groups = ["default"] +dependencies = [ + "typing-extensions>=4.1.0; python_version < \"3.11\"", +] +files = [ + {file = "multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee"}, + {file = "multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2"}, + {file = "multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37"}, + {file = "multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1"}, + {file = "multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b"}, + {file = "multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d"}, + {file = "multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f"}, + {file = "multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5"}, + {file = "multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581"}, + {file = "multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a"}, + {file = "multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d"}, + {file = "multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9"}, + {file = "multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2"}, + {file = "multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7"}, + {file = "multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5"}, + {file = "multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2"}, + {file = "multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56"}, + {file = "multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d"}, +] + [[package]] name = "packaging" version = "26.0" @@ -314,13 +428,13 @@ files = [ [[package]] name = "platformdirs" -version = "4.9.2" +version = "4.9.4" requires_python = ">=3.10" summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." groups = ["default"] files = [ - {file = "platformdirs-4.9.2-py3-none-any.whl", hash = "sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd"}, - {file = "platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291"}, + {file = "platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868"}, + {file = "platformdirs-4.9.4.tar.gz", hash = "sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934"}, ] [[package]] @@ -336,21 +450,62 @@ files = [ [[package]] name = "porringer" -version = "0.2.1.dev86" +version = "0.2.1.dev88" requires_python = ">=3.14" summary = "" groups = ["default"] dependencies = [ - "httpx<1.0,>=0.28.1", + "aiohttp>=3.13.3", "packaging>=26.0", - "platformdirs>=4.9.2", + "platformdirs>=4.9.4", "pydantic>=2.12.5", "typer[all]>=0.24.1", "userpath>=1.9.2", ] files = [ - {file = "porringer-0.2.1.dev86-py3-none-any.whl", hash = "sha256:a18555e318ee1cff19f987482f42d1b95d950feb7163011ee16b291d7fc189a5"}, - {file = "porringer-0.2.1.dev86.tar.gz", hash = "sha256:aae21088dfb11d133a0ce6fa3ac64b1f43bdaabaee6ed8a44e34362ef718cdc7"}, + {file = "porringer-0.2.1.dev88-py3-none-any.whl", hash = "sha256:94655eb1b834c8e7f9eaad3c38adc7b88d352b06aad1636dd4769f4b0f377ce8"}, + {file = "porringer-0.2.1.dev88.tar.gz", hash = "sha256:6f00913ec2e634ce873878da28407a83dd2e57485e6a07939d27b1bb27047620"}, +] + +[[package]] +name = "propcache" +version = "0.4.1" +requires_python = ">=3.9" +summary = "Accelerated property cache" +groups = ["default"] +files = [ + {file = "propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12"}, + {file = "propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c"}, + {file = "propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded"}, + {file = "propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641"}, + {file = "propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4"}, + {file = "propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44"}, + {file = "propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d"}, + {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b"}, + {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e"}, + {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f"}, + {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49"}, + {file = "propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144"}, + {file = "propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f"}, + {file = "propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153"}, + {file = "propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992"}, + {file = "propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f"}, + {file = "propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393"}, + {file = "propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0"}, + {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a"}, + {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be"}, + {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc"}, + {file = "propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a"}, + {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89"}, + {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726"}, + {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367"}, + {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36"}, + {file = "propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455"}, + {file = "propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85"}, + {file = "propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1"}, + {file = "propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9"}, + {file = "propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237"}, + {file = "propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d"}, ] [[package]] @@ -765,25 +920,77 @@ files = [ [[package]] name = "velopack" -version = "0.0.1521.dev61717" +version = "0.0.1535.dev45597" requires_python = ">=3.8" summary = "Installer and automatic update framework for cross-platform desktop applications" groups = ["default"] files = [ - {file = "velopack-0.0.1521.dev61717-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c7414ad3064e3b3e9f0cab54482d1817782be0d463980bbdcfa00fa7129971d4"}, - {file = "velopack-0.0.1521.dev61717-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:4f5855490eca3e3439795e1881fc41083f03c68e99248f1460d4b654043151dd"}, - {file = "velopack-0.0.1521.dev61717-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e19850ed861e774fa84ef3c5f1a894a2f073ce87fef5f780e84fa53ce4db97a2"}, - {file = "velopack-0.0.1521.dev61717-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9f6d8d32e86076fe0911330ad476237e2cbd46e9f5bf10395d0b621869fd4700"}, - {file = "velopack-0.0.1521.dev61717-cp37-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fc98b73507216356c1465ff9d71e24e63c00ce42b7d7d46c61de2930b76a7ec"}, - {file = "velopack-0.0.1521.dev61717-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:745f4347ac5bfa005d91ec234f1d3877c23eeeae31cef0069d28658fb2c13091"}, - {file = "velopack-0.0.1521.dev61717-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9f2cc109498f97104a8957714c06bc0d0fb36445fd25128345bc48d6f798f8d4"}, - {file = "velopack-0.0.1521.dev61717-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a68e322a42c2a5945866ac6cf51551c86ed082005d1393d6c32bac9f6dff38df"}, - {file = "velopack-0.0.1521.dev61717-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a7429f3b7110a521c82dab68c8aa6023f95f67b3583781c229da95bad47792ce"}, - {file = "velopack-0.0.1521.dev61717-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:8ec5b3617953dfd33115c12bd052ea9c6fc9b6e44f298dc5dcbb52c34080350c"}, - {file = "velopack-0.0.1521.dev61717-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:b815709578fb1d20416f8fbced0dd9c4cd1d2bc890c34026a57618ff8afb217d"}, - {file = "velopack-0.0.1521.dev61717-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:84febbdb3f6bd85705c03b8537f60f68abea4ac9625ac0121018a7bad2b62616"}, - {file = "velopack-0.0.1521.dev61717-cp37-abi3-win32.whl", hash = "sha256:50498279faeb349bc8d82d1b2f33d1640ebdef6b7a70a92d1a5e6de0c885410d"}, - {file = "velopack-0.0.1521.dev61717-cp37-abi3-win_amd64.whl", hash = "sha256:cbe8a2961cf60a4f7f13e379ca97377dfd3d3644f5b2406dc2bc07683e770128"}, - {file = "velopack-0.0.1521.dev61717-cp37-abi3-win_arm64.whl", hash = "sha256:8a042eda1d42f0d7da8edbe2f073a17eba885964505383f505a6dc4435664024"}, - {file = "velopack-0.0.1521.dev61717.tar.gz", hash = "sha256:b6d30caad90bb0c72adb818db61164356ba3e91ca45fa466cbaf55790de57eed"}, + {file = "velopack-0.0.1535.dev45597-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:5d39e8eeb4049fccf589e5ef33961dc113bd26cc56f4c6a7f4c5917f693cf9f0"}, + {file = "velopack-0.0.1535.dev45597-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:ebbfbc4c8f80e4c9a4f6956904e8ca5be040f1c6f13629f0c451de623a4b0bd4"}, + {file = "velopack-0.0.1535.dev45597-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9464b5d5f1b60e43dd59b4a1605aa7db6d79067e18702d1e665c929a6f179c5"}, + {file = "velopack-0.0.1535.dev45597-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fb7c5413e08cf68c4ee4b00741fdfc7c469e93a964fb50845497fc361e7826e5"}, + {file = "velopack-0.0.1535.dev45597-cp37-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3218a9a4ece39e21503d3754b79520b0d1ad650e554ffdbd3506579bf51c36d"}, + {file = "velopack-0.0.1535.dev45597-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c62e0aabcb100cbb68a9bec0aec281c1a4319a5c8937a5fe4bc8657d91c451ef"}, + {file = "velopack-0.0.1535.dev45597-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6c59342777a57ac6eb118cf92ae8466c36b75acaa3e9af21dffd8f0488411f80"}, + {file = "velopack-0.0.1535.dev45597-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d70121ff420c579a2be2bf8030e4480310eb16441b36ba7e337bec17a6c73d20"}, + {file = "velopack-0.0.1535.dev45597-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c59d66757accd8111078a0e2882c9a439893a06bc0a05ea7ec4e770f0e55c59a"}, + {file = "velopack-0.0.1535.dev45597-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:d309ee109a37ef9f871e4cb6b72e7841c012a18b2b1a82eaac8639ffce3c58ec"}, + {file = "velopack-0.0.1535.dev45597-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:2f59711e757421448540aaf0a05b6d4c674711f9c54d4b1f65e0e8f539a9a66a"}, + {file = "velopack-0.0.1535.dev45597-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a4940789d7b7649d4262b0d4987783d53d7d78e9718858a057ceec4181a33602"}, + {file = "velopack-0.0.1535.dev45597-cp37-abi3-win32.whl", hash = "sha256:a5a2aab05ddbf2e721c89cd997a20f851a2ec1e06647c2c98139770436fc1f62"}, + {file = "velopack-0.0.1535.dev45597-cp37-abi3-win_amd64.whl", hash = "sha256:931d4a3991172b19d6d3aaeb27e9a19b03f17c8b508613b01d8fce4be0f601e3"}, + {file = "velopack-0.0.1535.dev45597-cp37-abi3-win_arm64.whl", hash = "sha256:30afbf5211ca2f97d302600faded972c934b0e6a267af022f97b4297b30bc2e9"}, + {file = "velopack-0.0.1535.dev45597.tar.gz", hash = "sha256:c20a91eea1fc143db390bfcba63d995a9ffffc88bcdcdf39b843358a128df978"}, +] + +[[package]] +name = "yarl" +version = "1.23.0" +requires_python = ">=3.10" +summary = "Yet another URL library" +groups = ["default"] +dependencies = [ + "idna>=2.0", + "multidict>=4.0", + "propcache>=0.2.1", +] +files = [ + {file = "yarl-1.23.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:21d1b7305a71a15b4794b5ff22e8eef96ff4a6d7f9657155e5aa419444b28912"}, + {file = "yarl-1.23.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:85610b4f27f69984932a7abbe52703688de3724d9f72bceb1cca667deff27474"}, + {file = "yarl-1.23.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23f371bd662cf44a7630d4d113101eafc0cfa7518a2760d20760b26021454719"}, + {file = "yarl-1.23.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a80f77dc1acaaa61f0934176fccca7096d9b1ff08c8ba9cddf5ae034a24319"}, + {file = "yarl-1.23.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:bd654fad46d8d9e823afbb4f87c79160b5a374ed1ff5bde24e542e6ba8f41434"}, + {file = "yarl-1.23.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:682bae25f0a0dd23a056739f23a134db9f52a63e2afd6bfb37ddc76292bbd723"}, + {file = "yarl-1.23.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a82836cab5f197a0514235aaf7ffccdc886ccdaa2324bc0aafdd4ae898103039"}, + {file = "yarl-1.23.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c57676bdedc94cd3bc37724cf6f8cd2779f02f6aba48de45feca073e714fe52"}, + {file = "yarl-1.23.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c7f8dc16c498ff06497c015642333219871effba93e4a2e8604a06264aca5c5c"}, + {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5ee586fb17ff8f90c91cf73c6108a434b02d69925f44f5f8e0d7f2f260607eae"}, + {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:17235362f580149742739cc3828b80e24029d08cbb9c4bda0242c7b5bc610a8e"}, + {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0793e2bd0cf14234983bbb371591e6bea9e876ddf6896cdcc93450996b0b5c85"}, + {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3650dc2480f94f7116c364096bc84b1d602f44224ef7d5c7208425915c0475dd"}, + {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f40e782d49630ad384db66d4d8b73ff4f1b8955dc12e26b09a3e3af064b3b9d6"}, + {file = "yarl-1.23.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94f8575fbdf81749008d980c17796097e645574a3b8c28ee313931068dad14fe"}, + {file = "yarl-1.23.0-cp314-cp314-win32.whl", hash = "sha256:c8aa34a5c864db1087d911a0b902d60d203ea3607d91f615acd3f3108ac32169"}, + {file = "yarl-1.23.0-cp314-cp314-win_amd64.whl", hash = "sha256:63e92247f383c85ab00dd0091e8c3fa331a96e865459f5ee80353c70a4a42d70"}, + {file = "yarl-1.23.0-cp314-cp314-win_arm64.whl", hash = "sha256:70efd20be968c76ece7baa8dafe04c5be06abc57f754d6f36f3741f7aa7a208e"}, + {file = "yarl-1.23.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9a18d6f9359e45722c064c97464ec883eb0e0366d33eda61cb19a244bf222679"}, + {file = "yarl-1.23.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2803ed8b21ca47a43da80a6fd1ed3019d30061f7061daa35ac54f63933409412"}, + {file = "yarl-1.23.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:394906945aa8b19fc14a61cf69743a868bb8c465efe85eee687109cc540b98f4"}, + {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71d006bee8397a4a89f469b8deb22469fe7508132d3c17fa6ed871e79832691c"}, + {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:62694e275c93d54f7ccedcfef57d42761b2aad5234b6be1f3e3026cae4001cd4"}, + {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31de1613658308efdb21ada98cbc86a97c181aa050ba22a808120bb5be3ab94"}, + {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb1e8b8d66c278b21d13b0a7ca22c41dd757a7c209c6b12c313e445c31dd3b28"}, + {file = "yarl-1.23.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50f9d8d531dfb767c565f348f33dd5139a6c43f5cbdf3f67da40d54241df93f6"}, + {file = "yarl-1.23.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:575aa4405a656e61a540f4a80eaa5260f2a38fff7bfdc4b5f611840d76e9e277"}, + {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:041b1a4cefacf65840b4e295c6985f334ba83c30607441ae3cf206a0eed1a2e4"}, + {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:d38c1e8231722c4ce40d7593f28d92b5fc72f3e9774fe73d7e800ec32299f63a"}, + {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d53834e23c015ee83a99377db6e5e37d8484f333edb03bd15b4bc312cc7254fb"}, + {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2e27c8841126e017dd2a054a95771569e6070b9ee1b133366d8b31beb5018a41"}, + {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:76855800ac56f878847a09ce6dba727c93ca2d89c9e9d63002d26b916810b0a2"}, + {file = "yarl-1.23.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e09fd068c2e169a7070d83d3bde728a4d48de0549f975290be3c108c02e499b4"}, + {file = "yarl-1.23.0-cp314-cp314t-win32.whl", hash = "sha256:73309162a6a571d4cbd3b6a1dcc703c7311843ae0d1578df6f09be4e98df38d4"}, + {file = "yarl-1.23.0-cp314-cp314t-win_amd64.whl", hash = "sha256:4503053d296bc6e4cbd1fad61cf3b6e33b939886c4f249ba7c78b602214fabe2"}, + {file = "yarl-1.23.0-cp314-cp314t-win_arm64.whl", hash = "sha256:44bb7bef4ea409384e3f8bc36c063d77ea1b8d4a5b2706956c0d6695f07dcc25"}, + {file = "yarl-1.23.0-py3-none-any.whl", hash = "sha256:a2df6afe50dea8ae15fa34c9f824a3ee958d785fd5d089063d960bae1daa0a3f"}, + {file = "yarl-1.23.0.tar.gz", hash = "sha256:53b1ea6ca88ebd4420379c330aea57e258408dd0df9af0992e5de2078dc9f5d5"}, ] diff --git a/pyproject.toml b/pyproject.toml index adf9968..7e7984c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,9 +14,9 @@ requires-python = ">=3.14, <3.15" dependencies = [ "pyside6>=6.10.2", "packaging>=26.0", - "porringer>=0.2.1.dev86", + "porringer>=0.2.1.dev88", "qasync>=0.28.0", - "velopack>=0.0.1521.dev61717", + "velopack>=0.0.1535.dev45597", "typer>=0.24.1", ] diff --git a/synodic_client/application/screen/action_card.py b/synodic_client/application/screen/action_card.py index 9b6f3d0..ccfee32 100644 --- a/synodic_client/application/screen/action_card.py +++ b/synodic_client/application/screen/action_card.py @@ -40,6 +40,7 @@ from synodic_client.application.theme import ( ACTION_CARD_COMMAND_STYLE, ACTION_CARD_DESC_STYLE, + ACTION_CARD_DISTRO_BADGE_STYLE, ACTION_CARD_EXECUTING_STYLE, ACTION_CARD_PACKAGE_STYLE, ACTION_CARD_SKELETON_BAR_STYLE, @@ -64,6 +65,7 @@ COPY_BTN_STYLE, COPY_FEEDBACK_MS, COPY_ICON, + WSL_DISTRO_HEADER_STYLE, ) logger = logging.getLogger(__name__) @@ -224,7 +226,7 @@ def _init_real_ui(self) -> None: outer.addWidget(self._build_command_row()) def _build_top_row(self) -> QHBoxLayout: - """Build the top row: type badge | package name ... version | status/spinner | prerelease.""" + """Build the top row: type badge | [distro badge] | package name ... version | status/spinner | prerelease.""" top = QHBoxLayout() top.setSpacing(8) @@ -232,6 +234,11 @@ def _build_top_row(self) -> QHBoxLayout: self._type_badge.setStyleSheet(ACTION_CARD_TYPE_BADGE_STYLE) top.addWidget(self._type_badge) + self._distro_badge = QLabel() + self._distro_badge.setStyleSheet(ACTION_CARD_DISTRO_BADGE_STYLE) + self._distro_badge.hide() + top.addWidget(self._distro_badge) + self._package_label = QLabel() self._package_label.setStyleSheet(ACTION_CARD_PACKAGE_STYLE) self._package_label.setTextInteractionFlags( @@ -360,6 +367,12 @@ def populate( if action.installer: self._type_badge.setToolTip(f'Plugin: {action.installer}') + if action.distro: + self._distro_badge.setText(action.distro) + self._distro_badge.show() + else: + self._distro_badge.hide() + package_text = str(action.package) if action.package else action.description self._package_label.setText(package_text) @@ -635,6 +648,32 @@ def mousePressEvent(self, event: QMouseEvent) -> None: super().mousePressEvent(event) +# --------------------------------------------------------------------------- +# _DistroGroupHeader — section divider between native and WSL action groups +# --------------------------------------------------------------------------- + + +class _DistroGroupHeader(QLabel): + """Thin section divider between native and per-distro action groups. + + Shows ``HOST`` for the native group or ``WSL — `` for each + WSL2 distro group. Only inserted when both native and WSL actions + are present. + """ + + def __init__(self, distro_name: str | None, parent: QWidget | None = None) -> None: + """Initialise the header. + + Args: + distro_name: WSL distro name, or ``None`` for the native group. + parent: Optional parent widget. + """ + label = 'HOST' if distro_name is None else f'WSL \u2014 {distro_name}'.upper() + super().__init__(label, parent) + self.setObjectName('wslDistroHeader') + self.setStyleSheet(WSL_DISTRO_HEADER_STYLE) + + # --------------------------------------------------------------------------- # ActionCardList — card container # --------------------------------------------------------------------------- @@ -667,6 +706,7 @@ def __init__(self, parent: QWidget | None = None) -> None: self._layout.addStretch() self._cards: list[ActionCard] = [] + self._group_headers: list[QLabel] = [] self._action_map: dict[SetupAction, ActionCard] = {} self._index_map: dict[int, ActionCard] = {} @@ -701,25 +741,58 @@ def populate( ) -> None: """Replace skeleton cards with real action cards. + When actions include WSL distro entries (``action.distro is not + None``), the list is split into groups. Native host actions + appear first under a ``HOST`` section header; each WSL distro + gets its own ``WSL — `` section header below. If all + actions are native, no headers are inserted. + Args: actions: The setup actions to display. plugin_installed: Plugin name → installed mapping. prerelease_overrides: Package names with user pre-release overrides. """ self.clear() - sorted_actions = sorted(actions, key=action_sort_key) - for act in sorted_actions: - card = ActionCard(self) - card.populate( - act, - plugin_installed=plugin_installed, - prerelease_overrides=prerelease_overrides, - ) - card.prerelease_toggled.connect(self.prerelease_toggled.emit) - card.navigate_to_tool.connect(self.navigate_to_tool.emit) - self._layout.insertWidget(self._layout.count() - 1, card) - self._cards.append(card) - self._action_map[act] = card + + # Partition into native and per-distro groups. + native_actions: list[SetupAction] = [] + distro_actions: dict[str, list[SetupAction]] = {} + for act in actions: + if act.distro is None: + native_actions.append(act) + else: + distro_actions.setdefault(act.distro, []).append(act) + + has_wsl = bool(distro_actions) + + def _add_group(group_actions: list[SetupAction]) -> None: + for act in sorted(group_actions, key=action_sort_key): + card = ActionCard(self) + card.populate( + act, + plugin_installed=plugin_installed, + prerelease_overrides=prerelease_overrides, + ) + card.prerelease_toggled.connect(self.prerelease_toggled.emit) + card.navigate_to_tool.connect(self.navigate_to_tool.emit) + self._layout.insertWidget(self._layout.count() - 1, card) + self._cards.append(card) + self._action_map[act] = card + + def _add_header(distro_name: str | None) -> None: + header = _DistroGroupHeader(distro_name, self) + self._layout.insertWidget(self._layout.count() - 1, header) + self._group_headers.append(header) + + # Native actions (with host header only when WSL groups also exist) + if has_wsl and native_actions: + _add_header(None) + _add_group(native_actions) + + # Per-distro groups + for distro_name in sorted(distro_actions): + _add_header(distro_name) + _add_group(distro_actions[distro_name]) # Build original-index → card mapping so callers can look up by # the action index porringer emits, which is independent of the @@ -776,7 +849,11 @@ def finalize_all_checking(self) -> None: card.finalize_checking() def clear(self) -> None: - """Remove all cards.""" + """Remove all cards and group headers.""" + for header in self._group_headers: + self._layout.removeWidget(header) + header.deleteLater() + self._group_headers.clear() for card in self._cards: self._layout.removeWidget(card) card.deleteLater() diff --git a/synodic_client/application/screen/screen.py b/synodic_client/application/screen/screen.py index b2f62e6..2e1f8e9 100644 --- a/synodic_client/application/screen/screen.py +++ b/synodic_client/application/screen/screen.py @@ -50,6 +50,7 @@ ) from synodic_client.application.screen.spinner import LoadingIndicator from synodic_client.application.screen.update_banner import UpdateBanner +from synodic_client.application.screen.wsl import WslView from synodic_client.application.theme import ( COMPACT_MARGINS, FILTER_CHIP_SPACING, @@ -1434,6 +1435,7 @@ class MainWindow(QMainWindow): _tabs: QTabWidget | None = None _tools_view: ToolsView | None = None _projects_view: ProjectsView | None = None + _wsl_view: WslView | None = None def __init__( self, @@ -1517,6 +1519,22 @@ def show(self) -> None: self._tabs.addTab(self._tools_view, 'Tools') self.tools_view_created.emit(self._tools_view) + # WSL tab — only on Windows hosts with WSL available. + try: + from porringer.plugin.wsl.utility import is_wsl_host + + if is_wsl_host(): + self._wsl_view = WslView( + self._porringer, + self._store, + self, + coordinator=self._coordinator, + package_store=self._package_store, + ) + self._tabs.addTab(self._wsl_view, 'WSL') + except Exception: + logger.debug('Could not initialise WSL tab', exc_info=True) + # Navigate-to-project: switch to Projects tab and select directory self._tools_view.navigate_to_project_requested.connect(self._navigate_to_project) @@ -1546,6 +1564,8 @@ def show(self) -> None: self._tools_view.refresh() if self._projects_view is not None: self._projects_view.refresh() + if self._wsl_view is not None: + self._wsl_view.refresh() def _navigate_to_project(self, path_str: str) -> None: """Switch to the Projects tab and select the given directory.""" diff --git a/synodic_client/application/screen/wsl.py b/synodic_client/application/screen/wsl.py new file mode 100644 index 0000000..3a9c9cf --- /dev/null +++ b/synodic_client/application/screen/wsl.py @@ -0,0 +1,419 @@ +"""WSL package management view. + +Shows packages installed in each WSL2 distro, using the same plugin row +widgets as :class:`~synodic_client.application.screen.screen.ToolsView` +but scoped per-distro. + +Only rendered when :func:`porringer.plugin.wsl.utility.is_wsl_host` +returns ``True`` (Windows + ``wsl`` on PATH). +""" + +from __future__ import annotations + +import asyncio +import logging +from typing import TYPE_CHECKING + +from porringer.schema.plugin import PluginKind +from PySide6.QtCore import Qt, Signal +from PySide6.QtWidgets import ( + QHBoxLayout, + QLabel, + QPushButton, + QScrollArea, + QVBoxLayout, + QWidget, +) + +from synodic_client.application.screen.plugin_row import ( + PluginProviderHeader, + PluginRow, + RowPhase, +) +from synodic_client.application.screen.schema import PackageEntry, PluginRowData +from synodic_client.application.screen.spinner import LoadingIndicator +from synodic_client.application.theme import ( + COMPACT_MARGINS, + PLUGIN_KIND_HEADER_STYLE, + PLUGIN_SECTION_SPACING, +) + +if TYPE_CHECKING: + from porringer.api import API + from porringer.schema import PluginInfo + + from synodic_client.application.config_store import ConfigStore + from synodic_client.application.data import DataCoordinator + from synodic_client.application.package_state import PackageStateStore + +logger = logging.getLogger(__name__) + +#: Plugin kinds queried when listing packages inside a WSL distro. +_WSL_QUERYABLE_KINDS = frozenset({PluginKind.TOOL, PluginKind.PACKAGE}) + + +# --------------------------------------------------------------------------- +# _WslDistroHeader — section divider for one WSL distro +# --------------------------------------------------------------------------- + + +class _WslDistroHeader(QLabel): + """Uppercase section divider labelling a WSL2 distro group. + + Styled like :class:`PluginKindHeader` but with a purple tint to + visually distinguish WSL sections from native host sections. + """ + + def __init__(self, distro_name: str, parent: QWidget | None = None) -> None: + super().__init__(distro_name.upper(), parent) + self.setObjectName('pluginKindHeader') + self.setStyleSheet(PLUGIN_KIND_HEADER_STYLE) + + +# --------------------------------------------------------------------------- +# WslView +# --------------------------------------------------------------------------- + + +class WslView(QWidget): + """Tool management view for packages installed inside WSL2 distros. + + Mirrors the structure of ``ToolsView`` — distro section headers, + per-plugin :class:`PluginProviderHeader` rows, and per-package + :class:`PluginRow` widgets — but queries are routed through the + ``distro=`` parameter on :meth:`porringer.PackageCommands.list`, + :meth:`~porringer.PackageCommands.upgrade`, and + :meth:`~porringer.PackageCommands.uninstall`. + + Each distro's packages are loaded in parallel. Packages are + displayed globally (WSL environments don't have per-project venvs + in the same sense as the host). + """ + + package_update_requested = Signal(str, str, str) + """Emitted with ``(distro, plugin_name, package_name)`` when update is clicked.""" + + package_remove_requested = Signal(str, str, str) + """Emitted with ``(distro, plugin_name, package_name)`` when remove is clicked.""" + + def __init__( + self, + porringer: API, + store: ConfigStore, + parent: QWidget | None = None, + *, + coordinator: DataCoordinator | None = None, + package_store: PackageStateStore | None = None, + ) -> None: + """Initialize the WSL view. + + Args: + porringer: The porringer API instance. + store: The centralised :class:`ConfigStore`. + parent: Optional parent widget. + coordinator: Shared data coordinator for reusing discovery results. + package_store: Shared package update state registry (currently unused + by WslView but accepted for API consistency with ToolsView). + """ + super().__init__(parent) + self._porringer = porringer + self._store = store + self._coordinator = coordinator + self._section_widgets: list[QWidget] = [] + self._plugin_info_map: dict[str, PluginInfo] = {} + self._refresh_in_progress = False + self._init_ui() + + # ------------------------------------------------------------------ + # UI construction + # ------------------------------------------------------------------ + + def _init_ui(self) -> None: + """Build the view layout: toolbar, scroll area, loading indicator.""" + outer = QVBoxLayout(self) + outer.setContentsMargins(*COMPACT_MARGINS) + + outer.addLayout(self._build_toolbar()) + + self._scroll = QScrollArea() + self._scroll.setWidgetResizable(True) + self._scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) + + self._container = QWidget() + self._container_layout = QVBoxLayout(self._container) + self._container_layout.setSpacing(PLUGIN_SECTION_SPACING) + self._container_layout.setContentsMargins(0, 0, 0, 0) + self._container_layout.addStretch() + + self._scroll.setWidget(self._container) + outer.addWidget(self._scroll) + + self._loading_indicator = LoadingIndicator('Loading WSL packages\u2026') + outer.addWidget(self._loading_indicator) + + def _build_toolbar(self) -> QHBoxLayout: + """Build the toolbar with a refresh button.""" + toolbar = QHBoxLayout() + toolbar.addStretch() + + self._refresh_btn = QPushButton('Refresh') + self._refresh_btn.setFlat(True) + self._refresh_btn.setToolTip('Re-scan WSL packages') + self._refresh_btn.clicked.connect(self.refresh) + toolbar.addWidget(self._refresh_btn) + + return toolbar + + # ------------------------------------------------------------------ + # Public API + # ------------------------------------------------------------------ + + def refresh(self) -> None: + """Schedule an asynchronous rebuild of the WSL package list.""" + if self._refresh_in_progress: + return + asyncio.create_task(self._async_refresh()) + + # ------------------------------------------------------------------ + # Async refresh pipeline + # ------------------------------------------------------------------ + + async def _async_refresh(self) -> None: + """Rebuild the WSL package tree from porringer data.""" + self._refresh_in_progress = True + self._scroll.hide() + self._loading_indicator.start() + self._refresh_btn.setEnabled(False) + + try: + data = await self._gather_wsl_data() + self._build_widget_tree(data) + except Exception: + logger.exception('Failed to refresh WSL packages') + finally: + self._loading_indicator.stop() + self._scroll.show() + self._refresh_btn.setEnabled(True) + self._refresh_in_progress = False + + async def _gather_wsl_data(self) -> dict[str, dict[str, list[PackageEntry]]]: + """Fetch packages for every available WSL2 distro. + + For each distro, all TOOL- and PACKAGE-kind plugins are queried + in parallel using ``porringer.package.list(..., distro=distro)``. + + Returns: + ``{distro_name: {plugin_name: [PackageEntry, ...]}}`` for + distros that have at least one non-empty plugin result. + """ + from porringer.plugin.wsl.utility import available_distros + + discovered = self._coordinator.discovered_plugins if self._coordinator else None + + if self._coordinator is not None: + snapshot = await self._coordinator.refresh() + plugins = snapshot.plugins + else: + plugins = await self._porringer.plugin.list() + + # Update the plugin-info lookup for PluginProviderHeader construction. + self._plugin_info_map = {p.name: p for p in plugins} + + queryable = [p for p in plugins if p.kind in _WSL_QUERYABLE_KINDS and p.installed] + + loop = asyncio.get_running_loop() + distros: list[str] = await loop.run_in_executor(None, available_distros) + + result: dict[str, dict[str, list[PackageEntry]]] = {} + + async def _fetch_distro(distro: str) -> None: + distro_pkgs: dict[str, list[PackageEntry]] = {} + + async def _fetch_plugin(plugin: PluginInfo) -> None: + try: + pkgs = await self._porringer.package.list( + plugin.name, + plugins=discovered, + distro=distro, + ) + if pkgs: + distro_pkgs[plugin.name] = [ + PackageEntry( + name=str(pkg.name), + version=str(pkg.version) if pkg.version else '', + host_tool=pkg.relation.host if pkg.relation else '', + ) + for pkg in pkgs + ] + except Exception: + logger.debug( + 'Could not list packages for %s in distro %s', + plugin.name, + distro, + exc_info=True, + ) + + async with asyncio.TaskGroup() as tg: + for plugin in queryable: + tg.create_task(_fetch_plugin(plugin)) + + if distro_pkgs: + result[distro] = distro_pkgs + + async with asyncio.TaskGroup() as tg: + for distro in distros: + tg.create_task(_fetch_distro(distro)) + + return result + + # ------------------------------------------------------------------ + # Widget tree construction + # ------------------------------------------------------------------ + + def _build_widget_tree(self, data: dict[str, dict[str, list[PackageEntry]]]) -> None: + """Clear and rebuild section widgets from *data*.""" + self._clear_section_widgets() + + if not data: + empty = QLabel('No WSL packages found.') + empty.setAlignment(Qt.AlignmentFlag.AlignCenter) + empty.setStyleSheet('color: grey; padding: 20px;') + self._insert_section_widget(empty) + return + + for distro_name in sorted(data): + header = _WslDistroHeader(distro_name, parent=self._container) + self._insert_section_widget(header) + + for plugin_name, packages in sorted(data[distro_name].items()): + plugin_info = self._plugin_info_map.get(plugin_name) + if plugin_info is not None: + provider = PluginProviderHeader( + plugin_info, + show_controls=False, + parent=self._container, + ) + self._insert_section_widget(provider) + + for entry in packages: + row = PluginRow( + PluginRowData( + name=entry.name, + version=entry.version, + plugin_name=plugin_name, + show_toggle=False, + is_global=True, + host_tool=entry.host_tool, + ), + parent=self._container, + ) + # Capture distro_name in the lambda so the handler + # knows which distro to route the operation to. + row.update_requested.connect( + lambda pn, pkg, d=distro_name: asyncio.create_task( + self._update_package(d, pn, pkg), + ), + ) + row.remove_requested.connect( + lambda pn, pkg, d=distro_name: asyncio.create_task( + self._remove_package(d, pn, pkg), + ), + ) + self._insert_section_widget(row) + + def _insert_section_widget(self, widget: QWidget) -> None: + """Append *widget* to the container layout above the trailing stretch.""" + idx = self._container_layout.count() - 1 + self._container_layout.insertWidget(idx, widget) + self._section_widgets.append(widget) + + def _clear_section_widgets(self) -> None: + """Remove and delete all current section widgets.""" + for widget in self._section_widgets: + self._container_layout.removeWidget(widget) + widget.deleteLater() + self._section_widgets.clear() + + # ------------------------------------------------------------------ + # Package operations + # ------------------------------------------------------------------ + + async def _update_package(self, distro: str, plugin_name: str, package_name: str) -> None: + """Upgrade *package_name* inside *distro* via *plugin_name*. + + Transitions the matching :class:`PluginRow` to the ``UPDATING`` + phase, executes the upgrade, then returns it to ``IDLE`` or + shows an inline error on failure. + """ + from porringer.core.schema import PackageRef + + discovered = self._coordinator.discovered_plugins if self._coordinator else None + row = self._find_row(plugin_name, package_name) + if row is not None: + row.set_phase(RowPhase.UPDATING) + + try: + ref = PackageRef(name=package_name) + action_result = await self._porringer.package.upgrade( + plugin_name, + ref, + plugins=discovered, + distro=distro, + ) + if not action_result.success and not action_result.skipped: + msg = action_result.message or 'Update failed' + logger.warning('WSL package update failed: %s/%s in %s: %s', plugin_name, package_name, distro, msg) + if row is not None: + row.set_error(msg) + else: + if row is not None: + row.set_phase(RowPhase.IDLE) + self.package_update_requested.emit(distro, plugin_name, package_name) + except Exception: + logger.exception('WSL update error: %s/%s in %s', plugin_name, package_name, distro) + if row is not None: + row.set_error('Update error — see log') + + async def _remove_package(self, distro: str, plugin_name: str, package_name: str) -> None: + """Uninstall *package_name* from *distro* via *plugin_name*. + + On success, triggers a refresh so the removed package disappears + from the list. On failure, shows an inline error on the row. + """ + from porringer.core.schema import PackageRef + + discovered = self._coordinator.discovered_plugins if self._coordinator else None + row = self._find_row(plugin_name, package_name) + + try: + ref = PackageRef(name=package_name) + action_result = await self._porringer.package.uninstall( + plugin_name, + ref, + plugins=discovered, + distro=distro, + ) + if action_result.success or action_result.skipped: + self.package_remove_requested.emit(distro, plugin_name, package_name) + # Refresh to remove the row from the list. + self.refresh() + else: + msg = action_result.message or 'Removal failed' + logger.warning('WSL package removal failed: %s/%s in %s: %s', plugin_name, package_name, distro, msg) + if row is not None: + row.set_error(msg) + except Exception: + logger.exception('WSL removal error: %s/%s in %s', plugin_name, package_name, distro) + if row is not None: + row.set_error('Removal error — see log') + + def _find_row(self, plugin_name: str, package_name: str) -> PluginRow | None: + """Return the first :class:`PluginRow` matching *plugin_name* and *package_name*.""" + for widget in self._section_widgets: + if ( + isinstance(widget, PluginRow) + and widget._plugin_name == plugin_name + and widget._package_name == package_name + ): + return widget + return None diff --git a/synodic_client/application/theme.py b/synodic_client/application/theme.py index 4c45fbc..08b8c85 100644 --- a/synodic_client/application/theme.py +++ b/synodic_client/application/theme.py @@ -448,6 +448,22 @@ ) """Small type badge (Package, Tool, Runtime, etc.) on each action card.""" +ACTION_CARD_DISTRO_BADGE_STYLE = ( + 'QLabel { font-size: 10px; padding: 1px 6px; border-radius: 3px; background: #3d2a50; color: #c5a0e0; }' +) +"""Muted purple pill showing the WSL distro name on a WSL action card.""" + +WSL_DISTRO_HEADER_STYLE = ( + 'QLabel#wslDistroHeader {' + ' font-size: 11px;' + ' font-weight: bold;' + ' color: #c5a0e0;' + ' padding: 8px 4px 4px 4px;' + ' border-bottom: 1px solid palette(mid);' + '}' +) +"""Section divider for WSL distro groups inside the action card list.""" + ACTION_CARD_PACKAGE_STYLE = 'font-weight: bold; font-size: 12px;' """Primary line: package name.""" diff --git a/synodic_client/operations/install.py b/synodic_client/operations/install.py index d2e9bff..80adeb8 100644 --- a/synodic_client/operations/install.py +++ b/synodic_client/operations/install.py @@ -225,6 +225,7 @@ async def preview_manifest( package=str(act.package.name) if act.package else None, constraint=act.package.constraint if act.package else None, installer=act.installer, + distro=act.distro, ) ) diff --git a/synodic_client/operations/schema.py b/synodic_client/operations/schema.py index 54dd21d..5c39368 100644 --- a/synodic_client/operations/schema.py +++ b/synodic_client/operations/schema.py @@ -355,6 +355,8 @@ class ActionInfo: package: str | None = None constraint: str | None = None installer: str | None = None + distro: str | None = None + """WSL2 distro name, or ``None`` for native host actions.""" @dataclass(frozen=True, slots=True) diff --git a/synodic_client/updater.py b/synodic_client/updater.py index 753c434..c861011 100644 --- a/synodic_client/updater.py +++ b/synodic_client/updater.py @@ -262,10 +262,9 @@ def _check_manifest_fallback(self) -> Any: # Simple semver comparison via packaging.version (accepts # semver pre-release tags like ``0.1.0-dev.79``). try: - if Version(ver) > Version(current_semver): - if best_ver is None or Version(ver) > Version(best_ver): - best = asset - best_ver = ver + if Version(ver) > Version(current_semver) and (best_ver is None or Version(ver) > Version(best_ver)): + best = asset + best_ver = ver except Exception: continue